# Good Example: React Component > A UI component that follows Atelier's UI/UX principles. Each aspect cites the principle it satisfies. ## The Component ```tsx import { useId, useState } from 'react'; import { Button } from './Button'; import { Spinner } from './Spinner'; type DeleteButtonProps = { /** The resource name to display in the confirmation */ resourceName: string; /** Called when the user confirms deletion */ onDelete: () => Promise; }; export function DeleteButton({ resourceName, onDelete }: DeleteButtonProps) { const [isConfirming, setIsConfirming] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const confirmId = useId(); const handleConfirm = async () => { setIsDeleting(true); try { await onDelete(); } finally { setIsDeleting(false); setIsConfirming(false); } }; if (isConfirming) { return ( Delete {resourceName}? This cannot be undone. ); } return ( ); } ``` ## What Makes It Good ### Single Responsibility (components.md §1) - The component does one thing: confirm and trigger a deletion. No "And" in the name. - The `onDelete` callback is the single output. The component owns the confirmation UI, not the deletion logic. ### Explicit Boundaries (components.md §3) - Props are typed (`DeleteButtonProps`). Required props are required. - `onDelete` returns a `Promise` — the caller knows it's async. - The component never reads global state. It receives `resourceName` and `onDelete`. ### Predictable State (components.md §4) - `isConfirming` and `isDeleting` are owned by the component (only it cares). - State is not duplicated. The parent does not know about confirmation. - The component transitions: idle → confirming → deleting → idle. ### Render Purity (components.md §5) - Given the same props and state, the component renders the same output. - Side effects (`onDelete`) are in the event handler, not in render. - `useId()` is deterministic per component instance (React guarantee). ### Accessible by Default (components.md §6, uiux P2) - The confirmation group has `role="group"` and `aria-labelledby`. - The Spinner has a `label` (screen reader announces "Deleting"). - Buttons have text labels (not icon-only). - Focus order is logical (confirm → cancel). - Keyboard-reachable (buttons are natively focusable). ### Forgiveness (UI/UX P5, P10 Reversibility) - Destructive action requires confirmation (P5). - "This cannot be undone" names the consequence (P3 Clarity). - "Cancel" is offered and is not disabled during deletion (the user can cancel the *next* action). - The state is reversible: `isConfirming` can be set back to `false` (P10). ### Style via Tokens (components.md §7) - `variant="danger"` and `variant="ghost"` reference design tokens, not raw colors. - No `style={{ color: 'red' }}` — the token system owns the visual. ### Feedback (UI/UX P4) - The button shows a Spinner while deleting (P4, P6 Performance perception). - The button is disabled while deleting (prevents double-click). - The label changes: "Yes, delete" → Spinner (state is communicated). ### Clarity (UI/UX P3) - "Delete {resourceName}? This cannot be undone." — specific, names the resource and the consequence. - No "Are you sure?" — vague. No "Submit" — wrong verb. ## What This Example Does NOT Do (And Why That's Good) - Does not use a `window.confirm()` dialog — not accessible, not styled, not composable. - Does not render a modal — the inline confirmation is lighter and less disruptive (P9 Simplicity). - Does not auto-delete on click — forgiveness (P5). - Does not hardcode "Project" — the resource name is a prop (composability, components.md §3).