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.
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 }
pnpm dlx shadcn add @01orbital/announcementnpx shadcn add @01orbital/announcementyarn shadcn add @01orbital/announcementbunx --bun shadcn add @01orbital/announcementFree 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/reactnpm install @phosphor-icons/reactyarn add @phosphor-icons/reactbun add @phosphor-icons/reactimport { 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>
)
}Announcement is built from 3 parts. In the example they nest like this:
Announcement
├─ AnnouncementText
└─ AnnouncementActionAnnouncement: 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.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.
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.
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.
npm: @base-ui/react, cn. The CLI installs them for you.
Registry pieces it installs with it:
badgeseparatorCompare
Every Editorial component side by side: whether it follows the theme or stays dark, how many parts and custom props it has, and whether it is free. Open one for its preview, API and install.
App Timeline
Vertical timeline as an ordered list: a ring-and-dot indicator or custom mark per item, a 1px rail joining them, title, description and small label. CSS variables tune dot size and alignment.