Skip to content
ssntl/ui

Announcement

A news pill with an inset badge that nests in its curve.

Installation

$ pnpm dlx shadcn@latest add @ssntl/announcement

Usage

tsx
import { Announcement, AnnouncementTag } from "@/components/ui/announcement"

<Announcement href="/news/gift-cards">
  <AnnouncementTag>New</AnnouncementTag>
  Gift cards are here
</Announcement>

Rules

What this component expects from you, as it ships in components/ui/announcement.md next to the code. The full file adds when to use it, keyboard, the UI foundations it applies and every shared rule.

components/ui/announcement.md
## Rules for Announcement

- **Alert + inset badge.** A pill badge 4px inside a pill, so the badge's radius is the outer radius minus the padding.
- The badge carries a word (New, Beta), and the link text says what is new.
- The whole pill is one target; the arrow moves 2px on hover and stays still with reduced motion.
Open the full rules file (opens in a new tab)

Code

components/ui/announcement.tsx
import * as React from "react"
import { ChevronRight } from "lucide-react"
import { Slot } from "radix-ui"

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

// A pill that links to news ("New · Gift cards are here"). Nested corners:
// the badge is a pill inset 4px inside a pill, so its radius is the outer
// one minus the padding, by construction. The badge carries a word, and the
// whole pill is one 40px target.
function Announcement({
  asChild = false,
  className,
  children,
  ...props
}: React.ComponentProps<"a"> & { asChild?: boolean }) {
  const Comp = asChild ? Slot.Root : "a"
  return (
    <Comp
      data-slot="announcement"
      className={cn(
        "group inline-flex min-h-10 max-w-full items-center gap-3 rounded-full bg-secondary p-1 pe-3 text-caption text-foreground",
        "transition-[background-color] duration-(--duration-fast) ease-out hover:bg-neutral-200 dark:hover:bg-neutral-600",
        "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
        className
      )}
      {...props}
    >
      <Slot.Slottable>{children}</Slot.Slottable>
      <ChevronRight
        aria-hidden
        strokeWidth={1.6}
        className="size-4 shrink-0 opacity-80 transition-[translate] duration-(--duration-fast) ease-out group-hover:translate-x-0.5 motion-reduce:transition-none rtl:rotate-180"
      />
    </Comp>
  )
}

/** The inset badge: a word, on the card surface. */
function AnnouncementTag({
  className,
  ...props
}: React.ComponentProps<"span">) {
  return (
    <span
      data-slot="announcement-tag"
      className={cn(
        "inline-flex h-8 shrink-0 items-center rounded-full bg-card px-3 font-medium elevation-1",
        className
      )}
      {...props}
    />
  )
}

export { Announcement, AnnouncementTag }