StatusPill
ui statusshellheader
Status-Pille der Kopfzeile (Lizenz/CT): Mono-Label mit Ampel-Punkt, als Trigger für ein DropdownMenu. Feste 32px-Höhe wie der AccountMenu-Trigger — beide stehen in derselben Header-Zeile. `StatusDot` gibt es einzeln für die Popover-Überschrift, damit Pille und Detailansicht dieselbe Ampel zeigen.
Punkt einzeln (Popover-Überschrift)
Abhängigkeiten
Barrierefreiheit
- Trigger trägt aria-haspopup="menu" und spiegelt den Popover-Zustand in aria-expanded.
- Die Ampel ist nie die einzige Information — aria-label benennt den Zustand in Worten.
Beispiele
Als Dropdown-Trigger
<DropdownMenu
ariaLabel="Lizenzstatus"
trigger={({ open, toggle }) => (
<StatusPill ok={isPro} label="Lizenz" open={open} onToggle={toggle}
ariaLabel={isPro ? "Lizenzstatus: Pro aktiv" : "Lizenzstatus: Basis"} />
)}
>
…
</DropdownMenu> Genutzte Tokens · 13
--bg--border--danger--ease-standard--fg--focus-ring--font-mono--motion-fast--muted--radius-pill--space-2--space-3--success
Quelldateien
components/ui/StatusPill.tsx tsx
import styles from './StatusPill.module.css';
/**
* Status-Pille der Kopfzeile (Lizenz/CT): Mono-Label in Versalien mit
* Ampel-Punkt, als Trigger für ein `DropdownMenu` gedacht. Store-frei
* (Softkey-Vertrag): Zustand und Popover-Inhalt verdrahtet die App.
*
* Lebte vorher als Kopie in presenter UND worship (`StatusIndicators`), mit
* eigener Höhe (~26px aus Padding) neben dem 32px-`AccountMenu`-Trigger —
* in derselben Header-Zeile las sich der Unterschied als Versehen. Jetzt
* teilen sich beide die feste Höhe und die Hover-/Focus-Sprache.
*/
export interface StatusPillProps {
/** Ampel: grün = in Ordnung, rot = Handlungsbedarf. */
ok: boolean;
label: string;
/** Popover offen? Hält den Hover-Zustand, solange das Menü offen ist. */
open?: boolean;
onToggle: () => void;
ariaLabel: string;
}
export function StatusPill({ ok, label, open = false, onToggle, ariaLabel }: StatusPillProps) {
return (
<button
type="button"
className={`${styles.pill} ${open ? styles.pillOpen : ''}`}
aria-haspopup="menu"
aria-expanded={open}
aria-label={ariaLabel}
onClick={onToggle}
>
<StatusDot ok={ok} />
{label}
</button>
);
}
/** Derselbe Ampel-Punkt für die Popover-Überschrift — Pille und Detailansicht
* sollen dieselbe Farbe aus derselben Quelle zeigen. */
export function StatusDot({ ok }: { ok: boolean }) {
return <span className={`${styles.dot} ${ok ? styles.dotOk : styles.dotBad}`} aria-hidden="true" />;
}
components/ui/StatusPill.module.css css
/* Feste 32px wie der AccountMenu-Trigger: beide stehen in derselben
Header-Zeile, und die alte inhaltsgetriebene Höhe (~26px aus 6px-Padding)
ließ die Pillen neben dem Profil-Button geschrumpft wirken. Border, Radius,
Hover und Focus-Ring sind bewusst identisch zum Trigger — die Mono-Schrift
in Versalien bleibt das Erkennungszeichen der Status-Sprache. */
.pill {
display: inline-flex;
align-items: center;
gap: var(--space-2);
height: 32px;
padding: 0 var(--space-3);
border-radius: var(--radius-pill);
border: 1px solid var(--border);
background: var(--bg);
font-family: var(--font-mono);
font-size: 10px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
cursor: pointer;
transition:
border-color var(--motion-fast) var(--ease-standard),
color var(--motion-fast) var(--ease-standard);
}
.pill:hover,
.pillOpen {
border-color: color-mix(in oklab, var(--border), var(--fg) 25%);
color: var(--fg);
}
.pill:focus-visible {
outline: none;
box-shadow: var(--focus-ring);
}
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex: 0 0 auto;
}
.dotOk {
background: var(--success);
}
.dotBad {
background: var(--danger);
}
Holen
# MCP (Claude Code / Cursor)
get_component({ name: "status-pill" })
# Registry direkt
https://www.ekklesi.tools/design/registry/components/status-pill.json