Skip to content

Channel Welcome

The header that opens a channel, shown above its very first message.

The header that opens a channel, shown above its very first message. Compose it from the exported registry parts so local content and behaviour stay in the application while the visual contract stays token-driven.

import {
  ChannelWelcome,
  ChannelWelcomeAction,
  ChannelWelcomeDate,
  ChannelWelcomeDescription,
  ChannelWelcomeIcon,
  ChannelWelcomeTitle,
} from "@/registry/bases/newt/ui/channel-welcome"

export default function ChannelWelcomeDemo() {
  return (
    <ChannelWelcome>
      <ChannelWelcomeIcon />
      <ChannelWelcomeTitle>Welcome to #general!</ChannelWelcomeTitle>
      <ChannelWelcomeDescription>
        This is the start of the #general channel. Say hello and tell us what
        you are building.
      </ChannelWelcomeDescription>
      <ChannelWelcomeAction>
        <svg viewBox="0 0 16 16" className="h-4 w-4" fill="currentColor">
          <path d="m11.3 1.7 3 3-8.4 8.4-3.6.6.6-3.6 8.4-8.4Z" />
        </svg>
        Edit Channel
      </ChannelWelcomeAction>
      <ChannelWelcomeDate dateTime="2026-01-14">
        14 January 2026
      </ChannelWelcomeDate>
    </ChannelWelcome>
  )
}
<script setup lang="ts">
import {
  ChannelWelcome,
  ChannelWelcomeAction,
  ChannelWelcomeDate,
  ChannelWelcomeDescription,
  ChannelWelcomeIcon,
  ChannelWelcomeTitle,
} from "~~/registry/bases/newt/ui/channel-welcome"
</script>

<template>
  <ChannelWelcome>
    <ChannelWelcomeIcon />
    <ChannelWelcomeTitle>Welcome to #general!</ChannelWelcomeTitle>
    <ChannelWelcomeDescription>
      This is the start of the #general channel. Say hello and tell us what you
      are building.
    </ChannelWelcomeDescription>
    <ChannelWelcomeAction>
      <svg viewBox="0 0 16 16" class="h-4 w-4" fill="currentColor">
        <path d="m11.3 1.7 3 3-8.4 8.4-3.6.6.6-3.6 8.4-8.4Z" />
      </svg>
      Edit Channel
    </ChannelWelcomeAction>
    <ChannelWelcomeDate date-time="2026-01-14">
      14 January 2026
    </ChannelWelcomeDate>
  </ChannelWelcome>
</template>

Installation

pnpm dlx newtui@latest add channel-welcome
npx newtui@latest add channel-welcome
yarn dlx newtui@latest add channel-welcome
bunx newtui@latest add channel-welcome

Copy the source into your project

import * as React from "react"

import { cn } from "@/lib/utils"

/* The header that opens a channel, shown above its very first message. */
const ChannelWelcome = React.forwardRef<
  HTMLElement,
  React.ComponentProps<"section">
>(({ className, ...props }, ref) => (
  <section ref={ref} className={cn("px-4 pb-2 pt-10", className)} {...props} />
))
ChannelWelcome.displayName = "ChannelWelcome"

const ChannelWelcomeIcon = React.forwardRef<
  HTMLDivElement,
  React.ComponentProps<"div">
>(({ className, children = "#", ...props }, ref) => (
  <div
    ref={ref}
    aria-hidden="true"
    className={cn(
      "mb-4 flex h-[68px] w-[68px] items-center justify-center rounded-full bg-newt-bg-elevated text-[32px] font-bold leading-none text-newt-text-primary",
      className
    )}
    {...props}
  >
    {children}
  </div>
))
ChannelWelcomeIcon.displayName = "ChannelWelcomeIcon"

const ChannelWelcomeTitle = React.forwardRef<
  HTMLHeadingElement,
  React.ComponentProps<"h2">
>(({ className, ...props }, ref) => (
  <h2
    ref={ref}
    className={cn(
      "text-[28px] font-bold leading-tight text-newt-text-primary",
      className
    )}
    {...props}
  />
))
ChannelWelcomeTitle.displayName = "ChannelWelcomeTitle"

const ChannelWelcomeDescription = React.forwardRef<
  HTMLParagraphElement,
  React.ComponentProps<"p">
>(({ className, ...props }, ref) => (
  <p
    ref={ref}
    className={cn("mt-2 text-base text-newt-text-muted", className)}
    {...props}
  />
))
ChannelWelcomeDescription.displayName = "ChannelWelcomeDescription"

const ChannelWelcomeAction = React.forwardRef<
  HTMLButtonElement,
  React.ComponentProps<"button">
>(({ className, type = "button", ...props }, ref) => (
  <button
    ref={ref}
    type={type}
    className={cn(
      "mt-3 inline-flex cursor-pointer items-center gap-2 rounded-sm border-0 bg-newt-bg-elevated px-4 py-1.5 text-sm font-medium text-newt-text-primary transition-colors duration-fast ease-newt",
      "hover:bg-newt-bg-hover",
      "focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-newt-text-link",
      className
    )}
    {...props}
  />
))
ChannelWelcomeAction.displayName = "ChannelWelcomeAction"

export interface ChannelWelcomeDateProps extends Omit<
  React.ComponentProps<"div">,
  "children"
> {
  /** Machine-readable date for the rendered `<time>` element. */
  dateTime?: string
  children?: React.ReactNode
}

/* Creation date, centred between two hairlines. */
const ChannelWelcomeDate = React.forwardRef<
  HTMLDivElement,
  ChannelWelcomeDateProps
>(({ className, dateTime, children, ...props }, ref) => (
  <div
    ref={ref}
    className={cn(
      "mt-6 grid grid-cols-[1fr_auto_1fr] items-center gap-2 text-[12px] font-semibold uppercase tracking-wide text-newt-text-muted",
      "[&>span]:h-px [&>span]:bg-newt-border",
      className
    )}
    {...props}
  >
    <span aria-hidden="true" />
    <time dateTime={dateTime}>{children}</time>
    <span aria-hidden="true" />
  </div>
))
ChannelWelcomeDate.displayName = "ChannelWelcomeDate"

export {
  ChannelWelcome,
  ChannelWelcomeIcon,
  ChannelWelcomeTitle,
  ChannelWelcomeDescription,
  ChannelWelcomeAction,
  ChannelWelcomeDate,
}
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <section :class="cn('px-4 pb-2 pt-10', props.class)">
    <slot />
  </section>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <div
    aria-hidden="true"
    :class="
      cn(
        'mb-4 flex h-[68px] w-[68px] items-center justify-center rounded-full bg-newt-bg-elevated text-[32px] font-bold leading-none text-newt-text-primary',
        props.class
      )
    "
  >
    <slot>#</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>
  <h2
    :class="
      cn(
        'text-[28px] font-bold leading-tight text-newt-text-primary',
        props.class
      )
    "
  >
    <slot />
  </h2>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <p :class="cn('mt-2 text-base text-newt-text-muted', props.class)">
    <slot />
  </p>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <button
    type="button"
    :class="
      cn(
        'mt-3 inline-flex cursor-pointer items-center gap-2 rounded-sm border-0 bg-newt-bg-elevated px-4 py-1.5 text-sm font-medium text-newt-text-primary transition-colors duration-fast ease-newt',
        'hover:bg-newt-bg-hover',
        '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 = defineProps<{
  class?: HTMLAttributes["class"]
  /** Machine-readable date for the rendered `<time>` element. */
  dateTime?: string
}>()
</script>

<template>
  <!-- Creation date, centred between two hairlines. -->
  <div
    :class="
      cn(
        'mt-6 grid grid-cols-[1fr_auto_1fr] items-center gap-2 text-[12px] font-semibold uppercase tracking-wide text-newt-text-muted',
        '[&>span]:h-px [&>span]:bg-newt-border',
        props.class
      )
    "
  >
    <span aria-hidden="true" />
    <time :datetime="props.dateTime"><slot /></time>
    <span aria-hidden="true" />
  </div>
</template>
export { default as ChannelWelcome } from "./ChannelWelcome.vue"
export { default as ChannelWelcomeIcon } from "./ChannelWelcomeIcon.vue"
export { default as ChannelWelcomeTitle } from "./ChannelWelcomeTitle.vue"
export { default as ChannelWelcomeDescription } from "./ChannelWelcomeDescription.vue"
export { default as ChannelWelcomeAction } from "./ChannelWelcomeAction.vue"
export { default as ChannelWelcomeDate } from "./ChannelWelcomeDate.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 { ChannelWelcome } from "@/components/ui/channel-welcome"

export function Example() {
  return <ChannelWelcome />
}
<script setup lang="ts">
import { ChannelWelcome, ChannelWelcomeIcon, ChannelWelcomeTitle, ChannelWelcomeDescription, ChannelWelcomeAction, ChannelWelcomeDate } from "@/components/ui/channel-welcome"
</script>

<template>
  <ChannelWelcome />
</template>

API reference

ChannelWelcomeDate

PropType
dateTime?string | undefined

Machine-readable date for the rendered `<time>` element.

Typestring | undefined
children?React.ReactNode | undefined

Optional children value.

TypeReact.ReactNode | undefined

The registry exports ChannelWelcome, ChannelWelcomeIcon, ChannelWelcomeTitle, ChannelWelcomeDescription, ChannelWelcomeAction, ChannelWelcomeDate. Parts without a table accept the native attributes for the element they render; React uses className, while Vue uses class and fallthrough attributes.

Accessibility

Preserve the native semantics of the rendered elements and pass through ARIA attributes when the surrounding context needs a label or description. Any interactive child needs a visible focus indicator, and meaning must not rely on colour alone.

Tokens

Channel Welcome 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.