Editorial

Announcement

News pill link built on the secondary Badge, with a headline that truncates first and a Learn more action set off by a short vertical Separator. Renders an anchor through Base UI useRender.

Free, MIT licenseOpen in Browse
import type { ComponentProps } from "react"
import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render"
import { cn } from "cn"

import { badgeVariants } from "@/components/ui/badge"
import { Separator } from "@/components/ui/separator"

/**
 * A small pill that links to news, such as a launch, above a heading. It
 * renders an `<a>` styled as the secondary `Badge`, at reading size. Put an
 * `AnnouncementText` and an `AnnouncementAction` inside.
 *
 * Over artwork, add `className="dark"` so the pill takes the dark theme's
 * colors. Pass `render` to use your framework's link in place of the `<a>`.
 */
function Announcement({
  className,
  render,
  ...props
}: useRender.ComponentProps<"a">) {
  return useRender({
    defaultTagName: "a",
    render,
    state: { slot: "announcement" },
    props: mergeProps<"a">(
      {
        className: cn(
          badgeVariants({ variant: "secondary" }),
          "h-auto max-w-full gap-3 px-3 py-0.5 text-sm/6 font-normal",
          className
        ),
      },
      props
    ),
  })
}

/**
 * The headline inside `Announcement`. When space runs short it is cut off
 * with an ellipsis, while `AnnouncementAction` keeps its full width.
 */
function AnnouncementText({ className, ...props }: ComponentProps<"span">) {
  return (
    <span
      data-slot="announcement-text"
      className={cn("min-w-0 truncate", className)}
      {...props}
    />
  )
}

/**
 * The call to action at the end of `Announcement`, such as "Learn more" and a
 * caret icon. A short vertical rule sets it apart from `AnnouncementText`,
 * and it never shrinks.
 */
function AnnouncementAction({
  className,
  children,
  ...props
}: ComponentProps<"span">) {
  return (
    <span
      data-slot="announcement-action"
      className={cn(
        "inline-flex shrink-0 items-center gap-1.5 font-medium",
        className
      )}
      {...props}
    >
      <Separator
        aria-hidden="true"
        orientation="vertical"
        className="mr-1.5 data-vertical:h-3 data-vertical:self-center"
      />
      {children}
    </span>
  )
}

export { Announcement, AnnouncementText, AnnouncementAction }

Installation

pnpm dlx shadcn add @01orbital/announcement
npx shadcn add @01orbital/announcement
yarn shadcn add @01orbital/announcement
bunx --bun shadcn add @01orbital/announcement

Free under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.

It lands at components/editorial/announcement.tsx, as source you own and edit.

The example also imports @phosphor-icons/react from npm, which the install does not add:

pnpm add @phosphor-icons/react
npm install @phosphor-icons/react
yarn add @phosphor-icons/react
bun add @phosphor-icons/react

Usage

import { CaretRightIcon } from "@phosphor-icons/react/dist/ssr"

import {
  Announcement,
  AnnouncementAction,
  AnnouncementText,
} from "@/components/editorial/announcement"

const COPY = {
  text: "Rothkore design intelligence",
  cta: "Learn more",
  art: "https://01orbital.dev/art/kit/celestograph-51208-gesso-1b.png",
}

export default function AnnouncementDemo() {
  return (
    <div className="grid w-full place-items-center gap-6 p-6">
      <Announcement href="#">
        <AnnouncementText>{COPY.text}</AnnouncementText>
        <AnnouncementAction>
          {COPY.cta}
          <CaretRightIcon aria-hidden="true" />
        </AnnouncementAction>
      </Announcement>
      {/* Over art it takes the dark theme; a pale plate is the hardest case. */}
      <div className="relative isolate grid max-w-full place-items-center overflow-hidden rounded-xl p-6">
        <img
          src={COPY.art}
          alt=""
          className="absolute inset-0 size-full object-cover"
        />
        <Announcement href="#" className="dark relative">
          <AnnouncementText>{COPY.text}</AnnouncementText>
          <AnnouncementAction>
            {COPY.cta}
            <CaretRightIcon aria-hidden="true" />
          </AnnouncementAction>
        </Announcement>
      </div>
    </div>
  )
}

Anatomy

Announcement is built from 3 parts. In the example they nest like this:

Announcement
├─ AnnouncementText
└─ AnnouncementAction
  • Announcement: A small pill that links to news, such as a launch, above a heading. It renders an <a> styled as the secondary Badge, at reading size. Put an AnnouncementText and an AnnouncementAction inside.
  • AnnouncementText: The headline inside Announcement. When space runs short it is cut off with an ellipsis, while AnnouncementAction keeps its full width.
  • AnnouncementAction: The call to action at the end of Announcement, such as "Learn more" and a caret icon. A short vertical rule sets it apart from AnnouncementText, and it never shrinks.

API reference

Announcement

A small pill that links to news, such as a launch, above a heading. It renders an <a> styled as the secondary Badge, at reading size. Put an AnnouncementText and an AnnouncementAction inside.

Over artwork, add className="dark" so the pill takes the dark theme's colors. Pass render to use your framework's link in place of the <a>.

It accepts the props of the Base UI part it wraps, and the HTML attributes of the element it renders.

AnnouncementText

The headline inside Announcement. When space runs short it is cut off with an ellipsis, while AnnouncementAction keeps its full width.

It accepts the HTML attributes of the element it renders.

AnnouncementAction

The call to action at the end of Announcement, such as "Learn more" and a caret icon. A short vertical rule sets it apart from AnnouncementText, and it never shrinks.

It accepts the HTML attributes of the element it renders.

Dependencies

npm: @base-ui/react, cn. The CLI installs them for you.

Registry pieces it installs with it:

  • badge
  • separator