Channel Header
The bar above the message list, with the channel name, topic and channel actions.
The bar above the message list, with the channel name, topic and channel actions. Compose it from the exported registry parts so local content and behaviour stay in the application while the visual contract stays token-driven.
import {
ChannelHeader,
ChannelHeaderAction,
ChannelHeaderDivider,
ChannelHeaderIcon,
ChannelHeaderInfo,
ChannelHeaderName,
ChannelHeaderSearch,
ChannelHeaderToolbar,
ChannelHeaderTopic,
} from "@/registry/bases/newt/ui/channel-header"
export default function ChannelHeaderDemo() {
return (
<ChannelHeader className="w-full">
<ChannelHeaderInfo>
<ChannelHeaderIcon />
<ChannelHeaderName>general</ChannelHeaderName>
<ChannelHeaderDivider />
<ChannelHeaderTopic>
Say hello and tell us what you are building.
</ChannelHeaderTopic>
</ChannelHeaderInfo>
<ChannelHeaderToolbar>
<ChannelHeaderAction aria-label="Pinned messages">
<svg viewBox="0 0 24 24" className="h-6 w-6" fill="currentColor">
<path d="M14 3v6l3 3v2h-4v6l-1 1-1-1v-6H7v-2l3-3V3h4Z" />
</svg>
</ChannelHeaderAction>
<ChannelHeaderAction active aria-label="Hide member list" aria-pressed>
<svg viewBox="0 0 24 24" className="h-6 w-6" fill="currentColor">
<path d="M9 11a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Zm7 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM2 19a7 7 0 0 1 14 0v1H2v-1Zm15.5-6a5.5 5.5 0 0 1 4.5 5.4V20h-4v-1a8.9 8.9 0 0 0-1.6-5.1c.35-.06.72-.09 1.1-.09Z" />
</svg>
</ChannelHeaderAction>
<ChannelHeaderSearch className="hidden sm:flex" />
</ChannelHeaderToolbar>
</ChannelHeader>
)
}<script setup lang="ts">
import {
ChannelHeader,
ChannelHeaderAction,
ChannelHeaderDivider,
ChannelHeaderIcon,
ChannelHeaderInfo,
ChannelHeaderName,
ChannelHeaderSearch,
ChannelHeaderToolbar,
ChannelHeaderTopic,
} from "~~/registry/bases/newt/ui/channel-header"
</script>
<template>
<ChannelHeader class="w-full">
<ChannelHeaderInfo>
<ChannelHeaderIcon />
<ChannelHeaderName>general</ChannelHeaderName>
<ChannelHeaderDivider />
<ChannelHeaderTopic>
Say hello and tell us what you are building.
</ChannelHeaderTopic>
</ChannelHeaderInfo>
<ChannelHeaderToolbar>
<ChannelHeaderAction aria-label="Pinned messages">
<svg viewBox="0 0 24 24" class="h-6 w-6" fill="currentColor">
<path d="M14 3v6l3 3v2h-4v6l-1 1-1-1v-6H7v-2l3-3V3h4Z" />
</svg>
</ChannelHeaderAction>
<ChannelHeaderAction
active
aria-label="Hide member list"
aria-pressed="true"
>
<svg viewBox="0 0 24 24" class="h-6 w-6" fill="currentColor">
<path
d="M9 11a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Zm7 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM2 19a7 7 0 0 1 14 0v1H2v-1Zm15.5-6a5.5 5.5 0 0 1 4.5 5.4V20h-4v-1a8.9 8.9 0 0 0-1.6-5.1c.35-.06.72-.09 1.1-.09Z"
/>
</svg>
</ChannelHeaderAction>
<ChannelHeaderSearch class="hidden sm:flex" />
</ChannelHeaderToolbar>
</ChannelHeader>
</template>Installation
pnpm dlx newtui@latest add channel-headernpx newtui@latest add channel-headeryarn dlx newtui@latest add channel-headerbunx newtui@latest add channel-headerCopy the source into your project
import * as React from "react"
import { cn } from "@/lib/utils"
/* The bar above the message list: channel name, topic, and channel actions. */
const ChannelHeader = React.forwardRef<
HTMLElement,
React.ComponentProps<"header">
>(({ className, ...props }, ref) => (
<header
ref={ref}
className={cn(
"flex h-12 items-center justify-between gap-3 border-b border-newt-border bg-newt-bg-surface ps-4 pe-3 text-newt-text-primary",
className
)}
{...props}
/>
))
ChannelHeader.displayName = "ChannelHeader"
const ChannelHeaderInfo = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex min-w-0 items-center gap-2", className)}
{...props}
/>
))
ChannelHeaderInfo.displayName = "ChannelHeaderInfo"
const ChannelHeaderIcon = React.forwardRef<
HTMLSpanElement,
React.ComponentProps<"span">
>(({ className, children = "#", ...props }, ref) => (
<span
ref={ref}
aria-hidden="true"
className={cn(
"shrink-0 text-xl font-bold leading-none text-newt-text-muted",
className
)}
{...props}
>
{children}
</span>
))
ChannelHeaderIcon.displayName = "ChannelHeaderIcon"
const ChannelHeaderName = React.forwardRef<
HTMLSpanElement,
React.ComponentProps<"span">
>(({ className, ...props }, ref) => (
<span
ref={ref}
className={cn(
"shrink-0 truncate text-base font-semibold text-newt-text-primary",
className
)}
{...props}
/>
))
ChannelHeaderName.displayName = "ChannelHeaderName"
/* Round dot that sets the topic apart from the channel name. */
const ChannelHeaderDivider = React.forwardRef<
HTMLSpanElement,
React.ComponentProps<"span">
>(({ className, ...props }, ref) => (
<span
ref={ref}
aria-hidden="true"
className={cn(
"h-1 w-1 shrink-0 rounded-full bg-newt-text-muted opacity-70",
className
)}
{...props}
/>
))
ChannelHeaderDivider.displayName = "ChannelHeaderDivider"
const ChannelHeaderTopic = React.forwardRef<
HTMLSpanElement,
React.ComponentProps<"span">
>(({ className, ...props }, ref) => (
<span
ref={ref}
className={cn(
"min-w-0 truncate text-[13px] font-medium text-newt-text-muted",
className
)}
{...props}
/>
))
ChannelHeaderTopic.displayName = "ChannelHeaderTopic"
const ChannelHeaderToolbar = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex shrink-0 items-center gap-4", className)}
{...props}
/>
))
ChannelHeaderToolbar.displayName = "ChannelHeaderToolbar"
export interface ChannelHeaderActionProps extends React.ComponentProps<"button"> {
/** Keeps a toggled action (such as the member list) lit up. */
active?: boolean
}
const ChannelHeaderAction = React.forwardRef<
HTMLButtonElement,
ChannelHeaderActionProps
>(({ className, active = false, type = "button", ...props }, ref) => (
<button
ref={ref}
type={type}
data-state={active ? "active" : "inactive"}
className={cn(
"inline-flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center border-0 bg-transparent p-0 text-newt-text-muted transition-colors duration-fast ease-newt",
"hover:text-newt-text-primary data-[state=active]:text-newt-text-primary",
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-newt-text-link",
className
)}
{...props}
/>
))
ChannelHeaderAction.displayName = "ChannelHeaderAction"
const SearchIcon = () => (
<svg
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
strokeWidth="1.75"
strokeLinecap="round"
aria-hidden="true"
className="h-4 w-4 shrink-0"
>
<circle cx="7" cy="7" r="4.5" />
<path d="M10.5 10.5 14 14" />
</svg>
)
export interface ChannelHeaderSearchProps extends React.ComponentProps<"button"> {
placeholder?: string
}
const ChannelHeaderSearch = React.forwardRef<
HTMLButtonElement,
ChannelHeaderSearchProps
>(
(
{
className,
placeholder = "Search",
type = "button",
children,
"aria-label": ariaLabel = placeholder,
...props
},
ref
) => (
<button
ref={ref}
type={type}
aria-label={ariaLabel}
className={cn(
"flex h-7 w-60 shrink-0 cursor-pointer items-center justify-between gap-3 rounded-sm border border-newt-border bg-newt-bg-input px-2 text-newt-text-muted transition-colors duration-fast ease-newt",
"hover:border-newt-text-muted",
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-newt-text-link",
className
)}
{...props}
>
<span className="min-w-0 flex-1 truncate text-start text-sm font-medium leading-none">
{children ?? placeholder}
</span>
<SearchIcon />
</button>
)
)
ChannelHeaderSearch.displayName = "ChannelHeaderSearch"
/* Compact "N Online" readout used by the narrow layout of the header. */
const ChannelHeaderOnline = React.forwardRef<
HTMLSpanElement,
React.ComponentProps<"span">
>(({ className, children, ...props }, ref) => (
<span
ref={ref}
className={cn(
"inline-flex items-center gap-1.5 text-xs text-newt-text-muted",
className
)}
{...props}
>
<span
aria-hidden="true"
className="h-2 w-2 shrink-0 rounded-full bg-newt-online"
/>
{children}
</span>
))
ChannelHeaderOnline.displayName = "ChannelHeaderOnline"
export {
ChannelHeader,
ChannelHeaderInfo,
ChannelHeaderIcon,
ChannelHeaderName,
ChannelHeaderDivider,
ChannelHeaderTopic,
ChannelHeaderToolbar,
ChannelHeaderAction,
ChannelHeaderSearch,
ChannelHeaderOnline,
}<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<header
:class="
cn(
'flex h-12 items-center justify-between gap-3 border-b border-newt-border bg-newt-bg-surface ps-4 pe-3 text-newt-text-primary',
props.class
)
"
>
<slot />
</header>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<div :class="cn('flex min-w-0 items-center gap-2', props.class)">
<slot />
</div>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<span
aria-hidden="true"
:class="
cn(
'shrink-0 text-xl font-bold leading-none text-newt-text-muted',
props.class
)
"
>
<slot>#</slot>
</span>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<span
:class="
cn(
'shrink-0 truncate text-base font-semibold text-newt-text-primary',
props.class
)
"
>
<slot />
</span>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<!-- Round dot that sets the topic apart from the channel name. -->
<span
aria-hidden="true"
:class="
cn(
'h-1 w-1 shrink-0 rounded-full bg-newt-text-muted opacity-70',
props.class
)
"
/>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<span
:class="
cn(
'min-w-0 truncate text-[13px] font-medium text-newt-text-muted',
props.class
)
"
>
<slot />
</span>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<div :class="cn('flex shrink-0 items-center gap-4', props.class)">
<slot />
</div>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = withDefaults(
defineProps<{
class?: HTMLAttributes["class"]
/** Keeps a toggled action (such as the member list) lit up. */
active?: boolean
}>(),
{ active: false }
)
</script>
<template>
<button
type="button"
:data-state="props.active ? 'active' : 'inactive'"
:class="
cn(
'inline-flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center border-0 bg-transparent p-0 text-newt-text-muted transition-colors duration-fast ease-newt',
'hover:text-newt-text-primary data-[state=active]:text-newt-text-primary',
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-newt-text-link',
props.class
)
"
>
<slot />
</button>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = withDefaults(
defineProps<{ class?: HTMLAttributes["class"]; placeholder?: string }>(),
{ placeholder: "Search" }
)
</script>
<template>
<button
type="button"
:aria-label="props.placeholder"
:class="
cn(
'flex h-7 w-60 shrink-0 cursor-pointer items-center justify-between gap-3 rounded-sm border border-newt-border bg-newt-bg-input px-2 text-newt-text-muted transition-colors duration-fast ease-newt',
'hover:border-newt-text-muted',
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-newt-text-link',
props.class
)
"
>
<span
class="min-w-0 flex-1 truncate text-start text-sm font-medium leading-none"
>
<slot>{{ props.placeholder }}</slot>
</span>
<svg
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
stroke-width="1.75"
stroke-linecap="round"
aria-hidden="true"
class="h-4 w-4 shrink-0"
>
<circle cx="7" cy="7" r="4.5" />
<path d="M10.5 10.5 14 14" />
</svg>
</button>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<!-- Compact "N Online" readout used by the narrow layout of the header. -->
<span
:class="
cn(
'inline-flex items-center gap-1.5 text-xs text-newt-text-muted',
props.class
)
"
>
<span
aria-hidden="true"
class="h-2 w-2 shrink-0 rounded-full bg-newt-online"
/>
<slot />
</span>
</template>export { default as ChannelHeader } from "./ChannelHeader.vue"
export { default as ChannelHeaderInfo } from "./ChannelHeaderInfo.vue"
export { default as ChannelHeaderIcon } from "./ChannelHeaderIcon.vue"
export { default as ChannelHeaderName } from "./ChannelHeaderName.vue"
export { default as ChannelHeaderDivider } from "./ChannelHeaderDivider.vue"
export { default as ChannelHeaderTopic } from "./ChannelHeaderTopic.vue"
export { default as ChannelHeaderToolbar } from "./ChannelHeaderToolbar.vue"
export { default as ChannelHeaderAction } from "./ChannelHeaderAction.vue"
export { default as ChannelHeaderSearch } from "./ChannelHeaderSearch.vue"
export { default as ChannelHeaderOnline } from "./ChannelHeaderOnline.vue"Update the import paths
The files import @/lib/utils and sibling components through the aliases in your components.json. Point them at wherever those live in your project.
Usage
import { ChannelHeader } from "@/components/ui/channel-header"
export function Example() {
return <ChannelHeader />
}<script setup lang="ts">
import { ChannelHeader, ChannelHeaderInfo, ChannelHeaderIcon, ChannelHeaderName, ChannelHeaderDivider, ChannelHeaderTopic, ChannelHeaderToolbar, ChannelHeaderAction, ChannelHeaderSearch, ChannelHeaderOnline } from "@/components/ui/channel-header"
</script>
<template>
<ChannelHeader />
</template>API reference
ChannelHeaderAction
active?boolean | undefined
Keeps a toggled action (such as the member list) lit up.
boolean | undefinedChannelHeaderSearch
placeholder?string | undefined
Optional placeholder value.
string | undefinedThe registry exports ChannelHeader, ChannelHeaderInfo, ChannelHeaderIcon, ChannelHeaderName, ChannelHeaderDivider, ChannelHeaderTopic, ChannelHeaderToolbar, ChannelHeaderAction, ChannelHeaderSearch, ChannelHeaderOnline. Parts without a table accept the native attributes for the element they render; React uses className, while Vue uses class and fallthrough attributes.
Accessibility
Every icon-only toolbar action needs an aria-label. Preserve the visible focus ring and keep the channel name in the accessible heading hierarchy.
Tokens
Channel Header reads its colours, radii and motion from the --newt-* tokens installed with the library. Override a token and every component that reads it follows, including this one — see the token layer.