Components · Feedback
Announcement bar
One line at the top of the site on an inverted ground, a read-more link and a close button.
3.8% of the 2,062 sites in The UI Index have a alert (78 sites).
Both previews render the same markup; the HTML tab below is that markup without a framework.
Install
- React
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ announcement.json - Astro
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ astro/ announcement.json
Or copy the files below into your project. They need clsx, tailwind-merge, and the Caseroom theme. The HTML needs only the compiled CSS and the script.
// Announcement bar: the close button hides the bar; with data-remember it stays closed for the session
// (sessionStorage, by the bar's id) while you browse; focus moves to the page's main landmark (or the body).
declare global {
interface Window {
__caseroomAnnouncement?: boolean;
}
}
if (typeof window !== 'undefined' && !window.__caseroomAnnouncement) {
window.__caseroomAnnouncement = true;
const restore = () => {
for (const bar of document.querySelectorAll<HTMLElement>('[data-announcement]'))
if (bar.id && bar.hasAttribute('data-remember') && sessionStorage.getItem(`caseroom-announcement:${bar.id}`) === 'closed') bar.hidden = true;
};
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', restore);
else restore();
document.addEventListener('click', (e) => {
const b = (e.target as Element).closest<HTMLButtonElement>('[data-announcement-close]');
const bar = b?.closest<HTMLElement>('[data-announcement]');
if (!bar) return;
bar.hidden = true;
if (bar.id && bar.hasAttribute('data-remember')) sessionStorage.setItem(`caseroom-announcement:${bar.id}`, 'closed');
const main = document.querySelector<HTMLElement>('main');
if (main) {
if (!main.hasAttribute('tabindex')) main.tabIndex = -1;
main.focus({ preventScroll: true });
}
});
}
export {};import { cn } from '@/lib/utils';
import './announcement.client';
import { announcement as c, ARROW_PATH, CLOSE_PATH } from './announcement.variants';
export interface AnnouncementProps {
id: string;
/** Keep it closed for the session once closed (remembered under the id). */
remember?: boolean;
text: string;
link?: { label: string; href: string };
className?: string;
}
/** One line at the top of the site, a read-more link, and a close button remembered for the session. */
export function Announcement({ id, text, link, remember, className }: AnnouncementProps) {
return (
<aside id={id} data-announcement="" data-remember={remember ? '' : undefined} aria-label="Announcement" className={cn(c.root, className)}>
<div className={c.inner}>
<p className={c.text}>{text}</p>
{link && <a href={link.href} className={c.link}>{link.label}<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d={ARROW_PATH} /></svg></a>}
</div>
<button type="button" data-announcement-close="" aria-label="Close the announcement" className={c.close}>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" aria-hidden="true"><path d={CLOSE_PATH} /></svg>
</button>
</aside>
);
}/** An announcement bar (the most common alert in The UI Index): one line at the very top, a link to read more, and
* a close button that hides it and remembers that for the session. On an inverted ground, so it reads as the
* site's voice and not as page content. Centred: it is one line. */
export const announcement = {
root: 'inverted relative bg-background text-foreground',
inner: 'mx-auto flex min-h-10 max-w-6xl items-center justify-center gap-x-3 gap-y-1 px-12 py-2 text-center text-sm max-sm:flex-wrap',
text: 'text-pretty',
link: 'inline-flex min-h-6 items-center gap-1 rounded-sm font-medium underline decoration-foreground/40 underline-offset-4 transition-colors hover:decoration-foreground active:bg-accent [&_svg]:size-3.5',
close: 'absolute top-1/2 right-2 inline-flex size-8 -translate-y-1/2 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground active:bg-pressed [&_svg]:size-4',
};
export const ARROW_PATH = 'M3 8h10M9 4l4 4-4 4';
export const CLOSE_PATH = 'M4 4l8 8M12 4l-8 8';---
import { cn } from '@/lib/utils';
import { announcement as c, ARROW_PATH, CLOSE_PATH } from './announcement.variants';
interface Props {
id: string;
/** Keep it closed for the session once closed (remembered under the id). */
remember?: boolean;
text: string;
link?: { label: string; href: string };
class?: string;
}
const { id, text, link, remember, class: className } = Astro.props;
---
<aside id={id} data-announcement data-remember={remember ? '' : undefined} aria-label="Announcement" class={cn(c.root, className)}>
<div class={c.inner}>
<p class={c.text}>{text}</p>
{link && <a href={link.href} class={c.link}>{link.label}<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d={ARROW_PATH} /></svg></a>}
</div>
<button type="button" data-announcement-close aria-label="Close the announcement" class={c.close}>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><path d={CLOSE_PATH} /></svg>
</button>
</aside>
<script>
import './announcement.client';
</script>// Announcement bar: the close button hides the bar; with data-remember it stays closed for the session
// (sessionStorage, by the bar's id) while you browse; focus moves to the page's main landmark (or the body).
declare global {
interface Window {
__caseroomAnnouncement?: boolean;
}
}
if (typeof window !== 'undefined' && !window.__caseroomAnnouncement) {
window.__caseroomAnnouncement = true;
const restore = () => {
for (const bar of document.querySelectorAll<HTMLElement>('[data-announcement]'))
if (bar.id && bar.hasAttribute('data-remember') && sessionStorage.getItem(`caseroom-announcement:${bar.id}`) === 'closed') bar.hidden = true;
};
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', restore);
else restore();
document.addEventListener('click', (e) => {
const b = (e.target as Element).closest<HTMLButtonElement>('[data-announcement-close]');
const bar = b?.closest<HTMLElement>('[data-announcement]');
if (!bar) return;
bar.hidden = true;
if (bar.id && bar.hasAttribute('data-remember')) sessionStorage.setItem(`caseroom-announcement:${bar.id}`, 'closed');
const main = document.querySelector<HTMLElement>('main');
if (main) {
if (!main.hasAttribute('tabindex')) main.tabIndex = -1;
main.focus({ preventScroll: true });
}
});
}
export {};/** An announcement bar (the most common alert in The UI Index): one line at the very top, a link to read more, and
* a close button that hides it and remembers that for the session. On an inverted ground, so it reads as the
* site's voice and not as page content. Centred: it is one line. */
export const announcement = {
root: 'inverted relative bg-background text-foreground',
inner: 'mx-auto flex min-h-10 max-w-6xl items-center justify-center gap-x-3 gap-y-1 px-12 py-2 text-center text-sm max-sm:flex-wrap',
text: 'text-pretty',
link: 'inline-flex min-h-6 items-center gap-1 rounded-sm font-medium underline decoration-foreground/40 underline-offset-4 transition-colors hover:decoration-foreground active:bg-accent [&_svg]:size-3.5',
close: 'absolute top-1/2 right-2 inline-flex size-8 -translate-y-1/2 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground active:bg-pressed [&_svg]:size-4',
};
export const ARROW_PATH = 'M3 8h10M9 4l4 4-4 4';
export const CLOSE_PATH = 'M4 4l8 8M12 4l-8 8';<div class="w-full overflow-hidden rounded-lg">
<aside id="ex-announcement" data-announcement aria-label="Announcement" class="inverted relative bg-background text-foreground">
<div class="mx-auto flex min-h-10 max-w-6xl items-center justify-center gap-x-3 gap-y-1 px-12 py-2 text-center text-sm max-sm:flex-wrap">
<p class="text-pretty">Caseroom covers all 41 UI Index element types.</p>
<a href="#" class="inline-flex min-h-6 items-center gap-1 rounded-sm font-medium underline decoration-foreground/40 underline-offset-4 transition-colors hover:decoration-foreground active:bg-accent [&_svg]:size-3.5">
See the list
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 8h10M9 4l4 4-4 4"></path></svg>
</a>
</div>
<button type="button" data-announcement-close aria-label="Close the announcement" class="absolute top-1/2 right-2 inline-flex size-8 -translate-y-1/2 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground active:bg-pressed [&_svg]:size-4">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8"></path></svg>
</button>
</aside>
</div><script type="module" src="/caseroom/r/html/announcement.js"></script>