Sidebar Dashboard Shell
Sidebar, top bar and scrollable content slot, with a mobile drawer that closes on Escape and marks the active route.
Threshold alerts with the two facts most rule lists omit: how often each has fired, and whether its channel is muted — so a rule that is on but delivering nowhere reads as its own state.
components/dashboard-alert-rules.tsxnpx hoverlab add dashboard-alert-rules
Or over MCP, from your editor's agent — no account needed.
Free to read, copy and install, for personal and non-commercial projects. Shipping it in client work or a paid product needs Pro ($79 once). The source lands in your repo and stops being ours — no attribution, nothing to upgrade.
Start a page with this section — add more, order them, and leave with the page source.
4 of 5 active · 1 delivering nowhere
Alert when API error rate stays above 2% for 5 minutes.
Alert when p95 latency stays above 800ms for 15 minutes.
This rule is on, but #platform-alerts is muted — nothing has been delivered since it was muted.
Alert when Signups per hour stays below 4 for 1 hour.
Firing 142 times a month is usually a threshold problem rather than an incident. Consider raising it before muting the channel.
Alert when Job queue depth stays above 5,000 for 10 minutes.
Alert when Storage used stays above 85% for 1 day.
Rendered live in your current theme — this is the same component whose source is below, not a screenshot of it.
'use client'
/**
* <DashboardAlertRules> — thresholds that page someone, listed honestly.
*
* The dashboard tier can show you a number going wrong. It cannot tell you
* that a number went wrong at 3am on Sunday, and every product that reaches
* a second customer grows alert rules for exactly that reason.
*
* THE THING ALERT UIs USUALLY GET WRONG
*
* They render a rule as a sentence and a toggle, and omit the two facts
* that decide whether the rule is any good: how often it has fired, and
* where it goes. A rule that fired 140 times last month is not a rule, it
* is noise with a cron attached — and the person who muted the channel it
* posts to is the only one who knows. Both are on the row here, and a rule
* over the noise threshold is visibly marked rather than left for someone
* to notice.
*
* MUTED IS NOT OFF
*
* The third state is the important one. A rule can be enabled and still
* deliver nowhere, because its channel was muted somewhere else entirely.
* Most UIs show that rule as a healthy green toggle. Here it renders as its
* own state with its own sentence, because "on but going nowhere" is the
* state that gets an incident missed.
*
* NO DESTRUCTIVE DEFAULT. Deleting a rule is behind the row menu rather
* than a trash icon sitting under the cursor's natural resting place, and
* disabling is offered first — a rule that is wrong at 3am is usually
* wrong in its threshold, not in its existence.
*
* ACCESSIBILITY: the toggles are real checkboxes with `role="switch"` so
* their state is announced rather than implied by colour; the firing counts
* carry a visually hidden qualifier so "140" is read as "140 times in the
* last 30 days"; and severity is a word as well as a colour.
*/
import * as React from 'react'
import { AlertTriangle, BellOff, Hash, Mail, MoreHorizontal, Webhook } from 'lucide-react'
export type AlertSeverity = 'critical' | 'warning' | 'info'
export type AlertChannel = 'email' | 'slack' | 'webhook'
export interface AlertRule {
id: string
metric: string
comparator: 'above' | 'below'
threshold: string
/** Window the threshold is evaluated over, e.g. "5 minutes". */
window: string
severity: AlertSeverity
channel: AlertChannel
channelTarget: string
enabled: boolean
/** True when the destination is muted elsewhere — see the header. */
channelMuted?: boolean
/** Times this rule fired in the last 30 days. */
firedCount: number
}
export interface DashboardAlertRulesProps {
rules?: AlertRule[]
/** Firings above which a rule is called out as noisy. */
noisyThreshold?: number
className?: string
}
const DEFAULT_RULES: AlertRule[] = [
{
id: 'error-rate',
metric: 'API error rate',
comparator: 'above',
threshold: '2%',
window: '5 minutes',
severity: 'critical',
channel: 'slack',
channelTarget: '#incidents',
enabled: true,
firedCount: 3,
},
{
id: 'p95',
metric: 'p95 latency',
comparator: 'above',
threshold: '800ms',
window: '15 minutes',
severity: 'warning',
channel: 'slack',
channelTarget: '#platform-alerts',
enabled: true,
channelMuted: true,
firedCount: 27,
},
{
id: 'signups',
metric: 'Signups per hour',
comparator: 'below',
threshold: '4',
window: '1 hour',
severity: 'warning',
channel: 'email',
channelTarget: 'growth@acme.com',
enabled: true,
firedCount: 142,
},
{
id: 'queue',
metric: 'Job queue depth',
comparator: 'above',
threshold: '5,000',
window: '10 minutes',
severity: 'critical',
channel: 'webhook',
channelTarget: 'pagerduty.com/…/v2',
enabled: true,
firedCount: 1,
},
{
id: 'storage',
metric: 'Storage used',
comparator: 'above',
threshold: '85%',
window: '1 day',
severity: 'info',
channel: 'email',
channelTarget: 'ops@acme.com',
enabled: false,
firedCount: 0,
},
]
const SEVERITY_STYLES: Record<AlertSeverity, string> = {
critical: 'bg-destructive/10 text-destructive',
warning: 'bg-amber-500/10 text-amber-600 dark:text-amber-400',
info: 'bg-muted text-muted-foreground',
}
const CHANNEL_ICON: Record<AlertChannel, typeof Mail> = {
email: Mail,
slack: Hash,
webhook: Webhook,
}
export function DashboardAlertRules({
rules = DEFAULT_RULES,
noisyThreshold = 30,
className = '',
}: DashboardAlertRulesProps) {
const [state, setState] = React.useState(rules)
const toggle = (id: string) =>
setState((current) =>
current.map((rule) => (rule.id === id ? { ...rule, enabled: !rule.enabled } : rule)),
)
const active = state.filter((rule) => rule.enabled).length
const undelivered = state.filter((rule) => rule.enabled && rule.channelMuted).length
return (
<section
className={`rounded-2xl border border-border bg-card text-card-foreground ${className}`}
>
<header className="flex flex-wrap items-center justify-between gap-3 border-b border-border p-5 sm:p-6">
<div>
<h2 className="text-base font-semibold">Alert rules</h2>
<p className="mt-1 text-sm text-muted-foreground">
{active} of {state.length} active
{undelivered > 0 ? (
<>
{' · '}
<span className="font-medium text-amber-600 dark:text-amber-400">
{undelivered} delivering nowhere
</span>
</>
) : null}
</p>
</div>
<button
type="button"
className="rounded-lg bg-primary px-3 py-1.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
>
New rule
</button>
</header>
<ul className="divide-y divide-border">
{state.map((rule) => {
const ChannelIcon = CHANNEL_ICON[rule.channel]
const noisy = rule.firedCount >= noisyThreshold
const dark = rule.enabled && rule.channelMuted
return (
<li key={rule.id} className="flex flex-wrap items-start gap-4 p-5 sm:p-6">
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2">
<span
className={`rounded px-1.5 py-0.5 text-[11px] font-semibold uppercase tracking-wide ${SEVERITY_STYLES[rule.severity]}`}
>
{rule.severity}
</span>
<h3 data-stress-ignore className="text-sm font-semibold">{rule.metric}</h3>
</div>
{/* The rule as a sentence — the form people actually verify. */}
<p className="mt-1.5 text-sm text-muted-foreground">
Alert when {rule.metric} stays{' '}
<span className="font-medium text-foreground">
{rule.comparator} {rule.threshold}
</span>{' '}
for {rule.window}.
</p>
<div className="mt-2.5 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-muted-foreground">
<span className="inline-flex items-center gap-1.5">
<ChannelIcon aria-hidden className="h-3.5 w-3.5" />
{rule.channelTarget}
</span>
<span className={noisy ? 'font-medium text-amber-600 dark:text-amber-400' : ''}>
Fired {rule.firedCount}
<span className="sr-only"> times in the last 30 days</span>
<span aria-hidden> × / 30d</span>
</span>
</div>
{/*
The two states worth a sentence of their own. Both are
conditions a green toggle would otherwise hide.
*/}
{dark ? (
<p className="mt-3 flex items-start gap-2 rounded-lg bg-amber-500/10 p-2.5 text-xs text-amber-700 dark:text-amber-300">
<BellOff aria-hidden className="mt-0.5 h-3.5 w-3.5 shrink-0" />
This rule is on, but {rule.channelTarget} is muted — nothing has been
delivered since it was muted.
</p>
) : null}
{noisy && !dark ? (
<p className="mt-3 flex items-start gap-2 rounded-lg bg-muted p-2.5 text-xs text-muted-foreground">
<AlertTriangle aria-hidden className="mt-0.5 h-3.5 w-3.5 shrink-0" />
Firing {rule.firedCount} times a month is usually a threshold problem
rather than an incident. Consider raising it before muting the channel.
</p>
) : null}
</div>
<div className="flex items-center gap-1.5">
<button
type="button"
role="switch"
aria-checked={rule.enabled}
aria-label={`${rule.enabled ? 'Disable' : 'Enable'} ${rule.metric} alert`}
onClick={() => toggle(rule.id)}
className={`relative h-6 w-11 rounded-full border border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background ${
rule.enabled ? 'bg-primary' : 'bg-muted-foreground/30'
}`}
>
<span
aria-hidden
className={`absolute start-0 top-0.5 h-5 w-5 rounded-full border border-transparent bg-background transition-transform ${
rule.enabled
? 'translate-x-[22px] rtl:-translate-x-[22px]'
: 'translate-x-[2px] rtl:-translate-x-[2px]'
}`}
/>
</button>
<button
type="button"
aria-label={`More actions for ${rule.metric}`}
className="rounded-lg p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<MoreHorizontal aria-hidden className="h-4 w-4" />
</button>
</div>
</li>
)
})}
</ul>
</section>
)
}
bg-card, text-muted-foreground) — it inherits your theme instead of overriding it.Drop it at components/dashboard-alert-rules.tsx and import it where you need the section:
import { DashboardAlertRules } from '@/components/dashboard-alert-rules'1 of this block’s props are simple enough to drive from here. Change them and the block below re-renders — it is the same component whose source is above, not a mock of it. Everything else it accepts is in the table underneath.
Read out of the component’s own type and signature, so this cannot drift from the source below. Every prop has a default — the component renders standalone before you pass it anything.
| Prop | Type | Default |
|---|---|---|
rules | AlertRule[] | DEFAULT_RULES |
noisyThresholdFirings above which a rule is called out as noisy. | number | 30 |
className | string | '' |
The same block rendered once to markup, wrapped as a file your framework compiles. Tailwind classes are framework-agnostic, so the design transfers intact — the behaviour does not.
This block is interactive. The markup below is its initial state with the event handlers stripped — you will need to re-wire the behaviour in your framework.
<!--
Alert Rule List — markup from the Hoverlab catalog.
This is the block rendered once to HTML and wrapped as a component
file. It is not a port of the React source: the Tailwind classes carry
the design, which is the part that took the work, and they are the same
in every framework.
This block is interactive in React and the handlers are NOT here.
Buttons, toggles and menus render in their initial state and do
nothing until you wire them up.
-->
<section class="rounded-2xl border border-border bg-card text-card-foreground ">
<header class="flex flex-wrap items-center justify-between gap-3 border-b border-border p-5 sm:p-6">
<div>
<h2 class="text-base font-semibold">Alert rules</h2>
<p class="mt-1 text-sm text-muted-foreground">4 of 5 active · <span class="font-medium text-amber-600 dark:text-amber-400">1 delivering nowhere</span></p>
</div>
<button type="button" class="rounded-lg bg-primary px-3 py-1.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background">New rule</button>
</header>
<ul class="divide-y divide-border">
<li class="flex flex-wrap items-start gap-4 p-5 sm:p-6">
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
<span class="rounded px-1.5 py-0.5 text-[11px] font-semibold uppercase tracking-wide bg-destructive/10 text-destructive">critical</span>
<h3 data-stress-ignore="true" class="text-sm font-semibold">API error rate</h3>
</div>
<p class="mt-1.5 text-sm text-muted-foreground">Alert when API error rate stays <span class="font-medium text-foreground">above 2%</span> for 5 minutes.</p>
<div class="mt-2.5 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-muted-foreground">
<span class="inline-flex items-center gap-1.5"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-hash h-3.5 w-3.5" aria-hidden="true"><line x1="4" x2="20" y1="9" y2="9"></line><line x1="4" x2="20" y1="15" y2="15"></line><line x1="10" x2="8" y1="3" y2="21"></line><line x1="16" x2="14" y1="3" y2="21"></line></svg>#incidents</span>
<span class="">Fired 3<span class="sr-only"> times in the last 30 days</span><span aria-hidden="true"> × / 30d</span></span>
</div>
</div>
<div class="flex items-center gap-1.5">
<button type="button" role="switch" aria-checked="true" aria-label="Disable API error rate alert" class="relative h-6 w-11 rounded-full border border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background bg-primary">
<span aria-hidden="true" class="absolute start-0 top-0.5 h-5 w-5 rounded-full border border-transparent bg-background transition-transform translate-x-[22px] rtl:-translate-x-[22px]"></span>
</button>
<button type="button" aria-label="More actions for API error rate" class="rounded-lg p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-ellipsis h-4 w-4" aria-hidden="true">
<circle cx="12" cy="12" r="1"></circle>
<circle cx="19" cy="12" r="1"></circle>
<circle cx="5" cy="12" r="1"></circle>
</svg>
</button>
</div>
</li>
<li class="flex flex-wrap items-start gap-4 p-5 sm:p-6">
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
<span class="rounded px-1.5 py-0.5 text-[11px] font-semibold uppercase tracking-wide bg-amber-500/10 text-amber-600 dark:text-amber-400">warning</span>
<h3 data-stress-ignore="true" class="text-sm font-semibold">p95 latency</h3>
</div>
<p class="mt-1.5 text-sm text-muted-foreground">Alert when p95 latency stays <span class="font-medium text-foreground">above 800ms</span> for 15 minutes.</p>
<div class="mt-2.5 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-muted-foreground">
<span class="inline-flex items-center gap-1.5"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-hash h-3.5 w-3.5" aria-hidden="true"><line x1="4" x2="20" y1="9" y2="9"></line><line x1="4" x2="20" y1="15" y2="15"></line><line x1="10" x2="8" y1="3" y2="21"></line><line x1="16" x2="14" y1="3" y2="21"></line></svg>#platform-alerts</span>
<span class="">Fired 27<span class="sr-only"> times in the last 30 days</span><span aria-hidden="true"> × / 30d</span></span>
</div>
<p class="mt-3 flex items-start gap-2 rounded-lg bg-amber-500/10 p-2.5 text-xs text-amber-700 dark:text-amber-300"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bell-off mt-0.5 h-3.5 w-3.5 shrink-0" aria-hidden="true"><path d="M10.268 21a2 2 0 0 0 3.464 0"></path><path d="M17 17H4a1 1 0 0 1-.74-1.673C4.59 13.956 6 12.499 6 8a6 6 0 0 1 .258-1.742"></path><path d="m2 2 20 20"></path><path d="M8.668 3.01A6 6 0 0 1 18 8c0 2.687.77 4.653 1.707 6.05"></path></svg>This rule is on, but #platform-alerts is muted — nothing has been delivered since it was muted.</p>
</div>
<div class="flex items-center gap-1.5">
<button type="button" role="switch" aria-checked="true" aria-label="Disable p95 latency alert" class="relative h-6 w-11 rounded-full border border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background bg-primary">
<span aria-hidden="true" class="absolute start-0 top-0.5 h-5 w-5 rounded-full border border-transparent bg-background transition-transform translate-x-[22px] rtl:-translate-x-[22px]"></span>
</button>
<button type="button" aria-label="More actions for p95 latency" class="rounded-lg p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-ellipsis h-4 w-4" aria-hidden="true">
<circle cx="12" cy="12" r="1"></circle>
<circle cx="19" cy="12" r="1"></circle>
<circle cx="5" cy="12" r="1"></circle>
</svg>
</button>
</div>
</li>
<li class="flex flex-wrap items-start gap-4 p-5 sm:p-6">
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
<span class="rounded px-1.5 py-0.5 text-[11px] font-semibold uppercase tracking-wide bg-amber-500/10 text-amber-600 dark:text-amber-400">warning</span>
<h3 data-stress-ignore="true" class="text-sm font-semibold">Signups per hour</h3>
</div>
<p class="mt-1.5 text-sm text-muted-foreground">Alert when Signups per hour stays <span class="font-medium text-foreground">below 4</span> for 1 hour.</p>
<div class="mt-2.5 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-muted-foreground">
<span class="inline-flex items-center gap-1.5"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mail h-3.5 w-3.5" aria-hidden="true"><path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7"></path><rect x="2" y="4" width="20" height="16" rx="2"></rect></svg>growth@acme.com</span>
<span class="font-medium text-amber-600 dark:text-amber-400">Fired 142<span class="sr-only"> times in the last 30 days</span><span aria-hidden="true"> × / 30d</span></span>
</div>
<p class="mt-3 flex items-start gap-2 rounded-lg bg-muted p-2.5 text-xs text-muted-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert mt-0.5 h-3.5 w-3.5 shrink-0" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path></svg>Firing 142 times a month is usually a threshold problem rather than an incident. Consider raising it before muting the channel.</p>
</div>
<div class="flex items-center gap-1.5">
<button type="button" role="switch" aria-checked="true" aria-label="Disable Signups per hour alert" class="relative h-6 w-11 rounded-full border border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background bg-primary">
<span aria-hidden="true" class="absolute start-0 top-0.5 h-5 w-5 rounded-full border border-transparent bg-background transition-transform translate-x-[22px] rtl:-translate-x-[22px]"></span>
</button>
<button type="button" aria-label="More actions for Signups per hour" class="rounded-lg p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-ellipsis h-4 w-4" aria-hidden="true">
<circle cx="12" cy="12" r="1"></circle>
<circle cx="19" cy="12" r="1"></circle>
<circle cx="5" cy="12" r="1"></circle>
</svg>
</button>
</div>
</li>
<li class="flex flex-wrap items-start gap-4 p-5 sm:p-6">
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
<span class="rounded px-1.5 py-0.5 text-[11px] font-semibold uppercase tracking-wide bg-destructive/10 text-destructive">critical</span>
<h3 data-stress-ignore="true" class="text-sm font-semibold">Job queue depth</h3>
</div>
<p class="mt-1.5 text-sm text-muted-foreground">Alert when Job queue depth stays <span class="font-medium text-foreground">above 5,000</span> for 10 minutes.</p>
<div class="mt-2.5 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-muted-foreground">
<span class="inline-flex items-center gap-1.5"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-webhook h-3.5 w-3.5" aria-hidden="true"><path d="M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2"></path><path d="m6 17 3.13-5.78c.53-.97.1-2.18-.5-3.1a4 4 0 1 1 6.89-4.06"></path><path d="m12 6 3.13 5.73C15.66 12.7 16.9 13 18 13a4 4 0 0 1 0 8"></path></svg>pagerduty.com/…/v2</span>
<span class="">Fired 1<span class="sr-only"> times in the last 30 days</span><span aria-hidden="true"> × / 30d</span></span>
</div>
</div>
<div class="flex items-center gap-1.5">
<button type="button" role="switch" aria-checked="true" aria-label="Disable Job queue depth alert" class="relative h-6 w-11 rounded-full border border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background bg-primary">
<span aria-hidden="true" class="absolute start-0 top-0.5 h-5 w-5 rounded-full border border-transparent bg-background transition-transform translate-x-[22px] rtl:-translate-x-[22px]"></span>
</button>
<button type="button" aria-label="More actions for Job queue depth" class="rounded-lg p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-ellipsis h-4 w-4" aria-hidden="true">
<circle cx="12" cy="12" r="1"></circle>
<circle cx="19" cy="12" r="1"></circle>
<circle cx="5" cy="12" r="1"></circle>
</svg>
</button>
</div>
</li>
<li class="flex flex-wrap items-start gap-4 p-5 sm:p-6">
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
<span class="rounded px-1.5 py-0.5 text-[11px] font-semibold uppercase tracking-wide bg-muted text-muted-foreground">info</span>
<h3 data-stress-ignore="true" class="text-sm font-semibold">Storage used</h3>
</div>
<p class="mt-1.5 text-sm text-muted-foreground">Alert when Storage used stays <span class="font-medium text-foreground">above 85%</span> for 1 day.</p>
<div class="mt-2.5 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-muted-foreground">
<span class="inline-flex items-center gap-1.5"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mail h-3.5 w-3.5" aria-hidden="true"><path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7"></path><rect x="2" y="4" width="20" height="16" rx="2"></rect></svg>ops@acme.com</span>
<span class="">Fired 0<span class="sr-only"> times in the last 30 days</span><span aria-hidden="true"> × / 30d</span></span>
</div>
</div>
<div class="flex items-center gap-1.5">
<button type="button" role="switch" aria-checked="false" aria-label="Enable Storage used alert" class="relative h-6 w-11 rounded-full border border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background bg-muted-foreground/30">
<span aria-hidden="true" class="absolute start-0 top-0.5 h-5 w-5 rounded-full border border-transparent bg-background transition-transform translate-x-[2px] rtl:-translate-x-[2px]"></span>
</button>
<button type="button" aria-label="More actions for Storage used" class="rounded-lg p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-ellipsis h-4 w-4" aria-hidden="true">
<circle cx="12" cy="12" r="1"></circle>
<circle cx="19" cy="12" r="1"></circle>
<circle cx="5" cy="12" r="1"></circle>
</svg>
</button>
</div>
</li>
</ul>
</section>
What each framework gets across the whole catalog — effects convert properly; this rung is markup.
The component, its props, the design tokens it expects and the command that installs it — as one prompt. Paste it into Claude, Cursor, v0 or ChatGPT and what they build around it will match the rest of the catalog instead of inventing its own system.
Want the whole screen instead of this one section? Open a page and copy it entire.
All of them free to read, copy and install — no account, no locked tiles, no watermarked preview. The whole catalog is open, and so are the API and the CLI.
Browse DashboardsCopying the code is free. Putting it in client work or a paid product is what Pro is for — the licence, not the access.
Sidebar, top bar and scrollable content slot, with a mobile drawer that closes on Escape and marks the active route.
Metric row where each card knows whether a rise is good news, so churn going up is red while revenue going up is green.
Recent-events list with typed icons and relative timestamps that keep the absolute value in a <time> element.
Breadcrumb, title, action buttons and a counted tab row that sits flush with the bottom rule.
Four columns of prioritised, tagged, assigned task cards with a blocked state — the board as a layout contract, leaving drag behaviour to your own state and library.
Events grouped by day on a vertical rail, icons varying by type and one entry expanded into a detail card — what keeps forty events scannable instead of forty gray dots.
Saved views with the third state most implementations drop: a shared view with unsaved edits, where Save changes what a colleague sees tomorrow. Two honest buttons instead of one, ownership on the tab, and dirty said in words.
A reporting window with the comparison window spelled out beside it — resolved dates, UTC arithmetic, and an option to align weekdays so a weekend is never compared against a Tuesday.