Banner
Show an announcement across the top of your app, with an action link and optional dismissal.
import { Banner, BannerAction, BannerTitle } from '@youj/react'
Installation & usage
npm i @youj/reactFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Use a banner for news that concerns the whole product rather than one part of the page: a launch, an event, a trial running out, a maintenance window. It sits above everything, full bleed, and says one thing. For something that belongs to a section of the page, use an Alert. For feedback on something the user just did, use a Toast.
import { Banner, BannerAction, BannerTitle } from '@youj/react'
export function LaunchBanner() {
return (
<Banner variant="inverse" dismissible>
<BannerTitle>
<strong>Deploy previews</strong> <span>now comment on every pull request.</span>
</BannerTitle>
<BannerAction href="/changelog">Read the changelog</BannerAction>
</Banner>
)
}BannerTitle holds the message. A <strong> inside it leads in, and a <span> after it is set a step quieter. BannerAction is a link whose arrow nudges out and back on hover or focus. Anything else you put in the banner, like a badge, sits in the same centered row.
Description
A dismissible inverse banner above a page; the page glides up when it's dismissed.
dismissible adds a dismiss button at the end, outside the flow, so the message stays centered on the page. The banner collapses through Collapse: its height eases to zero while the content fades out faster, and the page below glides up. If focus was inside the banner, it moves on to the next focusable element.
Passing onOpenChange also makes a banner dismissible. Control it with open to remember the choice, for example in local storage, so a dismissed announcement stays dismissed. Starting closed and opening after mount keeps the server render and the first client render the same, and the banner expands into place:
import { useEffect, useState } from 'react'
import { Banner, BannerTitle } from '@youj/react'
export function PreviewsBanner() {
const [open, setOpen] = useState(false)
useEffect(() => setOpen(localStorage.getItem('banner:previews') !== 'dismissed'), [])
return (
<Banner
open={open}
onOpenChange={(next) => {
setOpen(next)
if (!next) localStorage.setItem('banner:previews', 'dismissed')
}}
>
<BannerTitle>Deploy previews now comment on every pull request.</BannerTitle>
</Banner>
)
}Banners in several looks, one with an icon.
variant is default, a recessed strip with a hairline; inverse, the foreground ink; accent, the theme's accent; or warning, an amber tint for maintenance and incidents. icon puts an icon before the message: a Youj icon with trigger="mount" plays once as the banner appears.
Code
banner-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propsicon, dismissible, open… (7)
Props
| Prop | Type | |
|---|---|---|
icon | ReactNodeA leading icon or badge, before the message. | – |
dismissible | booleanShow a dismiss button. Implied by | – |
open | booleanControlled visibility. | – |
defaultOpen | booleanUncontrolled initial visibility. default true | true |
onOpenChange | (open: boolean) => voidCalled with | – |
dismissLabel | stringAccessible name of the dismiss button. default "Dismiss announcement" | "Dismiss announcement" |
variant | 'default' | 'accent' | 'warning' | 'inverse'default 'default' | 'default' |
Other props pass through: extends ComponentProps<'div'>, VariantProps<typeof bannerVariants>. | ||
BannerTitle takes the props of a p, and BannerAction those of an a.
MotionAction arrow, dismiss, height, dismiss, content… · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Action arrowon hover or focus of the link | no token the arrow icon’s own nudge | the arrow icon’s own nudge | |
| Dismiss, heightthe page below glides up | duration.baseease.standard220ms · cubic-bezier(0.2, 0, 0, 1) | 220mscubic-bezier(0.2, 0, 0, 1) | |
| Dismiss, contentfades out ahead of the height | duration.fastease.exit140ms · cubic-bezier(0.4, 0, 1, 1) | 140mscubic-bezier(0.4, 0, 1, 1) | |
| Reopenheight and content together | duration.baseease.standard220ms · cubic-bezier(0.2, 0, 0, 1) | 220mscubic-bezier(0.2, 0, 0, 1) | |
| Link underline | duration.fastease.standard140ms · cubic-bezier(0.2, 0, 0, 1) | 140mscubic-bezier(0.2, 0, 0, 1) |
Reduced motion
A dismissed banner leaves at once and a reopened one appears at once. The arrow and any mount icon hold still.
A banner that renders with the page appears in place, without expanding. Reversing mid-collapse (setting open back to true) opens from the current height.
Reduced motion
A dismissed banner leaves at once and the page below moves up in the same frame. The action's arrow and any mount icon stay at rest. The look and the message don't change.
AccessibilityARIA, focus, announcements
Accessibility
- The banner is a
regionlandmark named "Announcement", so screen-reader users can find it or skip it. Passaria-labelto name it better, like "Maintenance". - It isn't a live region: a banner that renders with the page is read in place. If one appears later and has to be heard, announce it yourself.
- The dismiss button is named by
dismissLabel, "Dismiss announcement" by default. - When a dismissed banner collapses, focus inside it moves to the next focusable element, never to the body.
- The icon is decorative. The message carries the meaning.