@misoto22/design 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +87 -0
- package/CLAUDE.md +1 -0
- package/README.md +50 -5
- package/bin/misoto22-design.mjs +59 -13
- package/dist/agent/Accordion.md +24 -1
- package/dist/agent/Alert.md +23 -1
- package/dist/agent/AppShell.md +30 -2
- package/dist/agent/ArchitectureFigure.md +25 -2
- package/dist/agent/AreaChart.md +27 -2
- package/dist/agent/Article.md +26 -3
- package/dist/agent/AspectRatio.md +57 -0
- package/dist/agent/Avatar.md +25 -5
- package/dist/agent/Badge.md +22 -1
- package/dist/agent/BarChart.md +29 -2
- package/dist/agent/BarList.md +27 -1
- package/dist/agent/BigNumber.md +26 -2
- package/dist/agent/BoxPlot.md +24 -1
- package/dist/agent/Breadcrumb.md +24 -2
- package/dist/agent/BulletChart.md +23 -1
- package/dist/agent/Button.md +25 -1
- package/dist/agent/Calendar.md +37 -3
- package/dist/agent/Card.md +24 -2
- package/dist/agent/Checkbox.md +23 -2
- package/dist/agent/Code.md +50 -0
- package/dist/agent/CodeBlock.md +74 -0
- package/dist/agent/Collapsible.md +22 -1
- package/dist/agent/Combobox.md +29 -3
- package/dist/agent/Command.md +25 -2
- package/dist/agent/ComposedChart.md +26 -2
- package/dist/agent/ContextMenu.md +37 -4
- package/dist/agent/DataflowFigure.md +22 -1
- package/dist/agent/DatePicker.md +34 -6
- package/dist/agent/DescriptionList.md +68 -0
- package/dist/agent/Diagram.md +27 -3
- package/dist/agent/DiagramCanvas.md +24 -1
- package/dist/agent/DiagramExportMenu.md +29 -3
- package/dist/agent/DiagramInspector.md +23 -1
- package/dist/agent/DiagramLegend.md +22 -1
- package/dist/agent/DiagramMinimap.md +24 -4
- package/dist/agent/DiagramToolbar.md +23 -1
- package/dist/agent/Dialog.md +26 -3
- package/dist/agent/DropdownMenu.md +45 -6
- package/dist/agent/EmptyState.md +26 -1
- package/dist/agent/ErrorState.md +27 -2
- package/dist/agent/Facet.md +25 -1
- package/dist/agent/Field.md +45 -4
- package/dist/agent/FigureBand.md +24 -1
- package/dist/agent/FloatingIconButton.md +23 -1
- package/dist/agent/FunnelChart.md +23 -1
- package/dist/agent/Heading.md +64 -0
- package/dist/agent/Heatmap.md +25 -1
- package/dist/agent/Histogram.md +26 -2
- package/dist/agent/Input.md +22 -1
- package/dist/agent/Kbd.md +19 -1
- package/dist/agent/LifecycleFigure.md +22 -1
- package/dist/agent/LineChart.md +26 -2
- package/dist/agent/LinkArrow.md +20 -1
- package/dist/agent/Markdown.md +68 -0
- package/dist/agent/NativeSelect.md +23 -2
- package/dist/agent/NavItem.md +21 -1
- package/dist/agent/Pagination.md +28 -2
- package/dist/agent/PieChart.md +26 -2
- package/dist/agent/Popover.md +23 -1
- package/dist/agent/Progress.md +27 -3
- package/dist/agent/RadarChart.md +26 -2
- package/dist/agent/RadialChart.md +26 -3
- package/dist/agent/RadioGroup.md +23 -2
- package/dist/agent/SankeyChart.md +25 -2
- package/dist/agent/ScatterChart.md +27 -2
- package/dist/agent/ScrollArea.md +24 -3
- package/dist/agent/SearchableMenu.md +23 -1
- package/dist/agent/Select.md +29 -3
- package/dist/agent/Separator.md +28 -4
- package/dist/agent/SequenceFigure.md +23 -1
- package/dist/agent/Sheet.md +25 -2
- package/dist/agent/Skeleton.md +24 -2
- package/dist/agent/Slider.md +27 -4
- package/dist/agent/Sparkline.md +23 -1
- package/dist/agent/Spinner.md +24 -2
- package/dist/agent/StatusDot.md +20 -1
- package/dist/agent/StatusPill.md +28 -2
- package/dist/agent/Steps.md +24 -1
- package/dist/agent/Switch.md +21 -1
- package/dist/agent/Table.md +26 -3
- package/dist/agent/Tabs.md +24 -2
- package/dist/agent/Tag.md +40 -9
- package/dist/agent/Text.md +65 -0
- package/dist/agent/Textarea.md +20 -1
- package/dist/agent/Timestamp.md +64 -0
- package/dist/agent/Toast.md +26 -2
- package/dist/agent/ToggleGroup.md +24 -2
- package/dist/agent/Toolbar.md +71 -0
- package/dist/agent/Tooltip.md +23 -1
- package/dist/agent/TreemapChart.md +24 -1
- package/dist/agent/WaterfallChart.md +24 -1
- package/dist/agent/WorkflowFigure.md +24 -2
- package/dist/agent/catalog.json +5602 -745
- package/dist/agent/index.md +13 -4
- package/dist/article.css +16 -4
- package/dist/charts/AreaChart/AreaChart.d.ts +9 -2
- package/dist/charts/AreaChart/AreaChart.js +12 -13
- package/dist/charts/AreaChart/AreaChart.js.map +1 -1
- package/dist/charts/BarChart/BarChart.d.ts +14 -3
- package/dist/charts/BarChart/BarChart.js +20 -12
- package/dist/charts/BarChart/BarChart.js.map +1 -1
- package/dist/charts/BarList/BarList.d.ts +9 -1
- package/dist/charts/BarList/BarList.js +9 -1
- package/dist/charts/BarList/BarList.js.map +1 -1
- package/dist/charts/BigNumber/BigNumber.d.ts +19 -3
- package/dist/charts/BigNumber/BigNumber.js +25 -5
- package/dist/charts/BigNumber/BigNumber.js.map +1 -1
- package/dist/charts/BoxPlot/BoxPlot.js +4 -2
- package/dist/charts/BoxPlot/BoxPlot.js.map +1 -1
- package/dist/charts/BulletChart/BulletChart.js +28 -7
- package/dist/charts/BulletChart/BulletChart.js.map +1 -1
- package/dist/charts/ComposedChart/ComposedChart.d.ts +9 -2
- package/dist/charts/ComposedChart/ComposedChart.js +10 -10
- package/dist/charts/ComposedChart/ComposedChart.js.map +1 -1
- package/dist/charts/FunnelChart/FunnelChart.d.ts +7 -2
- package/dist/charts/FunnelChart/FunnelChart.js +6 -16
- package/dist/charts/FunnelChart/FunnelChart.js.map +1 -1
- package/dist/charts/Heatmap/Heatmap.d.ts +17 -3
- package/dist/charts/Heatmap/Heatmap.js +20 -11
- package/dist/charts/Heatmap/Heatmap.js.map +1 -1
- package/dist/charts/Histogram/Histogram.d.ts +24 -1
- package/dist/charts/Histogram/Histogram.js +43 -11
- package/dist/charts/Histogram/Histogram.js.map +1 -1
- package/dist/charts/LineChart/LineChart.d.ts +9 -2
- package/dist/charts/LineChart/LineChart.js +11 -12
- package/dist/charts/LineChart/LineChart.js.map +1 -1
- package/dist/charts/PieChart/PieChart.d.ts +15 -3
- package/dist/charts/PieChart/PieChart.js +14 -6
- package/dist/charts/PieChart/PieChart.js.map +1 -1
- package/dist/charts/RadarChart/RadarChart.d.ts +15 -3
- package/dist/charts/RadarChart/RadarChart.js +11 -9
- package/dist/charts/RadarChart/RadarChart.js.map +1 -1
- package/dist/charts/RadialChart/RadialChart.d.ts +22 -4
- package/dist/charts/RadialChart/RadialChart.js +28 -6
- package/dist/charts/RadialChart/RadialChart.js.map +1 -1
- package/dist/charts/SankeyChart/SankeyChart.d.ts +16 -4
- package/dist/charts/SankeyChart/SankeyChart.js +14 -6
- package/dist/charts/SankeyChart/SankeyChart.js.map +1 -1
- package/dist/charts/ScatterChart/ScatterChart.d.ts +24 -3
- package/dist/charts/ScatterChart/ScatterChart.js +21 -15
- package/dist/charts/ScatterChart/ScatterChart.js.map +1 -1
- package/dist/charts/Sparkline/Sparkline.js +5 -3
- package/dist/charts/Sparkline/Sparkline.js.map +1 -1
- package/dist/charts/TreemapChart/TreemapChart.d.ts +7 -2
- package/dist/charts/TreemapChart/TreemapChart.js +20 -11
- package/dist/charts/TreemapChart/TreemapChart.js.map +1 -1
- package/dist/charts/WaterfallChart/WaterfallChart.js +4 -2
- package/dist/charts/WaterfallChart/WaterfallChart.js.map +1 -1
- package/dist/charts/lib/export.d.ts +1 -0
- package/dist/charts/lib/figure.d.ts +21 -1
- package/dist/charts/lib/figure.js +5 -2
- package/dist/charts/lib/figure.js.map +1 -1
- package/dist/charts/lib/scale.d.ts +23 -0
- package/dist/charts/lib/scale.js +13 -0
- package/dist/charts/lib/scale.js.map +1 -0
- package/dist/charts/lib/selection.d.ts +20 -0
- package/dist/charts/lib/selection.js +18 -0
- package/dist/charts/lib/selection.js.map +1 -0
- package/dist/charts/lib/toolbar.d.ts +1 -0
- package/dist/components/Accordion/Accordion.js +8 -1
- package/dist/components/Accordion/Accordion.js.map +1 -1
- package/dist/components/AppShell/AppShell.d.ts +6 -0
- package/dist/components/AppShell/AppShell.js +34 -4
- package/dist/components/AppShell/AppShell.js.map +1 -1
- package/dist/components/Article/Article.d.ts +11 -4
- package/dist/components/Article/Article.js +13 -0
- package/dist/components/Article/Article.js.map +1 -1
- package/dist/components/AspectRatio/AspectRatio.d.ts +46 -0
- package/dist/components/AspectRatio/AspectRatio.js +33 -0
- package/dist/components/AspectRatio/AspectRatio.js.map +1 -0
- package/dist/components/Avatar/Avatar.d.ts +12 -3
- package/dist/components/Avatar/Avatar.js +6 -1
- package/dist/components/Avatar/Avatar.js.map +1 -1
- package/dist/components/Breadcrumb/Breadcrumb.d.ts +6 -0
- package/dist/components/Breadcrumb/Breadcrumb.js +15 -0
- package/dist/components/Breadcrumb/Breadcrumb.js.map +1 -1
- package/dist/components/Button/Button.js +14 -0
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Calendar/Calendar.d.ts +29 -3
- package/dist/components/Calendar/Calendar.js +60 -12
- package/dist/components/Calendar/Calendar.js.map +1 -1
- package/dist/components/Card/Card.d.ts +6 -0
- package/dist/components/Card/Card.js +1 -1
- package/dist/components/Card/Card.js.map +1 -1
- package/dist/components/Checkbox/Checkbox.d.ts +5 -2
- package/dist/components/Checkbox/Checkbox.js +12 -2
- package/dist/components/Checkbox/Checkbox.js.map +1 -1
- package/dist/components/Code/Code.d.ts +30 -0
- package/dist/components/Code/Code.js +21 -0
- package/dist/components/Code/Code.js.map +1 -0
- package/dist/components/CodeBlock/CodeBlock.d.ts +141 -0
- package/dist/components/CodeBlock/CodeBlock.js +123 -0
- package/dist/components/CodeBlock/CodeBlock.js.map +1 -0
- package/dist/components/Collapsible/Collapsible.js +10 -2
- package/dist/components/Collapsible/Collapsible.js.map +1 -1
- package/dist/components/Combobox/Combobox.d.ts +15 -1
- package/dist/components/Combobox/Combobox.js +25 -5
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Command/Command.d.ts +4 -2
- package/dist/components/Command/Command.js +13 -1
- package/dist/components/Command/Command.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.d.ts +30 -5
- package/dist/components/ContextMenu/ContextMenu.js +32 -2
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/components/DatePicker/DatePicker.d.ts +36 -4
- package/dist/components/DatePicker/DatePicker.js +55 -12
- package/dist/components/DatePicker/DatePicker.js.map +1 -1
- package/dist/components/DescriptionList/DescriptionList.d.ts +73 -0
- package/dist/components/DescriptionList/DescriptionList.js +35 -0
- package/dist/components/DescriptionList/DescriptionList.js.map +1 -0
- package/dist/components/Diagram/Diagram.d.ts +8 -3
- package/dist/components/Diagram/Diagram.js +82 -10
- package/dist/components/Diagram/Diagram.js.map +1 -1
- package/dist/components/Dialog/Dialog.d.ts +18 -3
- package/dist/components/Dialog/Dialog.js +20 -2
- package/dist/components/Dialog/Dialog.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.d.ts +49 -11
- package/dist/components/DropdownMenu/DropdownMenu.js +32 -2
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/EmptyState/EmptyState.d.ts +25 -1
- package/dist/components/EmptyState/EmptyState.js +3 -1
- package/dist/components/EmptyState/EmptyState.js.map +1 -1
- package/dist/components/ErrorState/ErrorState.d.ts +22 -1
- package/dist/components/ErrorState/ErrorState.js +11 -2
- package/dist/components/ErrorState/ErrorState.js.map +1 -1
- package/dist/components/Field/Field.d.ts +70 -3
- package/dist/components/Field/Field.js +53 -12
- package/dist/components/Field/Field.js.map +1 -1
- package/dist/components/Field/field-control.d.ts +31 -0
- package/dist/components/Field/field-control.js +12 -0
- package/dist/components/Field/field-control.js.map +1 -0
- package/dist/components/FloatingIconButton/FloatingIconButton.js +2 -0
- package/dist/components/FloatingIconButton/FloatingIconButton.js.map +1 -1
- package/dist/components/Heading/Heading.d.ts +62 -0
- package/dist/components/Heading/Heading.js +44 -0
- package/dist/components/Heading/Heading.js.map +1 -0
- package/dist/components/Markdown/Markdown.d.ts +139 -0
- package/dist/components/Markdown/Markdown.js +135 -0
- package/dist/components/Markdown/Markdown.js.map +1 -0
- package/dist/components/Markdown/inline.d.ts +29 -0
- package/dist/components/Markdown/inline.js +74 -0
- package/dist/components/Markdown/inline.js.map +1 -0
- package/dist/components/Markdown/nodes.d.ts +65 -0
- package/dist/components/Markdown/nodes.js +1 -0
- package/dist/components/Markdown/nodes.js.map +1 -0
- package/dist/components/Markdown/parse.d.ts +24 -0
- package/dist/components/Markdown/parse.js +110 -0
- package/dist/components/Markdown/parse.js.map +1 -0
- package/dist/components/Markdown/slug.d.ts +15 -0
- package/dist/components/Markdown/slug.js +7 -0
- package/dist/components/Markdown/slug.js.map +1 -0
- package/dist/components/NativeSelect/NativeSelect.d.ts +6 -0
- package/dist/components/NativeSelect/NativeSelect.js +2 -3
- package/dist/components/NativeSelect/NativeSelect.js.map +1 -1
- package/dist/components/Pagination/Pagination.d.ts +24 -1
- package/dist/components/Pagination/Pagination.js +6 -3
- package/dist/components/Pagination/Pagination.js.map +1 -1
- package/dist/components/Progress/Progress.d.ts +17 -5
- package/dist/components/Progress/Progress.js +18 -6
- package/dist/components/Progress/Progress.js.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.d.ts +6 -0
- package/dist/components/RadioGroup/RadioGroup.js +11 -1
- package/dist/components/RadioGroup/RadioGroup.js.map +1 -1
- package/dist/components/ScrollArea/ScrollArea.d.ts +15 -0
- package/dist/components/ScrollArea/ScrollArea.js +2 -2
- package/dist/components/ScrollArea/ScrollArea.js.map +1 -1
- package/dist/components/SearchableMenu/SearchableMenu.d.ts +11 -2
- package/dist/components/SearchableMenu/SearchableMenu.js +24 -2
- package/dist/components/SearchableMenu/SearchableMenu.js.map +1 -1
- package/dist/components/Select/Select.d.ts +18 -2
- package/dist/components/Select/Select.js +20 -3
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Separator/Separator.d.ts +21 -3
- package/dist/components/Separator/Separator.js +9 -1
- package/dist/components/Separator/Separator.js.map +1 -1
- package/dist/components/Sheet/Sheet.d.ts +3 -0
- package/dist/components/Sheet/Sheet.js +11 -2
- package/dist/components/Sheet/Sheet.js.map +1 -1
- package/dist/components/Skeleton/Skeleton.d.ts +8 -1
- package/dist/components/Skeleton/Skeleton.js +1 -1
- package/dist/components/Skeleton/Skeleton.js.map +1 -1
- package/dist/components/Slider/Slider.d.ts +21 -3
- package/dist/components/Slider/Slider.js +54 -27
- package/dist/components/Slider/Slider.js.map +1 -1
- package/dist/components/Spinner/Spinner.d.ts +8 -0
- package/dist/components/Spinner/Spinner.js +3 -2
- package/dist/components/Spinner/Spinner.js.map +1 -1
- package/dist/components/StatusPill/StatusPill.d.ts +5 -0
- package/dist/components/StatusPill/StatusPill.js +6 -0
- package/dist/components/StatusPill/StatusPill.js.map +1 -1
- package/dist/components/Table/Table.d.ts +6 -0
- package/dist/components/Table/Table.js +3 -1
- package/dist/components/Table/Table.js.map +1 -1
- package/dist/components/Tag/Tag.d.ts +68 -9
- package/dist/components/Tag/Tag.js +50 -10
- package/dist/components/Tag/Tag.js.map +1 -1
- package/dist/components/Text/Text.d.ts +62 -0
- package/dist/components/Text/Text.js +30 -0
- package/dist/components/Text/Text.js.map +1 -0
- package/dist/components/Timestamp/Timestamp.d.ts +59 -0
- package/dist/components/Timestamp/Timestamp.js +56 -0
- package/dist/components/Timestamp/Timestamp.js.map +1 -0
- package/dist/components/Toast/Toast.d.ts +9 -1
- package/dist/components/Toast/Toast.js +27 -4
- package/dist/components/Toast/Toast.js.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.d.ts +6 -1
- package/dist/components/ToggleGroup/ToggleGroup.js +11 -1
- package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
- package/dist/components/Toolbar/Toolbar.d.ts +55 -0
- package/dist/components/Toolbar/Toolbar.js +43 -0
- package/dist/components/Toolbar/Toolbar.js.map +1 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.d.ts +6 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js +48 -8
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js.map +1 -1
- package/dist/diagrams/DataflowFigure/DataflowFigure.js +21 -2
- package/dist/diagrams/DataflowFigure/DataflowFigure.js.map +1 -1
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.d.ts +14 -0
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.js +14 -7
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.js.map +1 -1
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.d.ts +38 -4
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js +14 -16
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js.map +1 -1
- package/dist/diagrams/DiagramInspector/DiagramInspector.js +2 -2
- package/dist/diagrams/DiagramInspector/DiagramInspector.js.map +1 -1
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.d.ts +23 -7
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.js +45 -18
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.js.map +1 -1
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.js +44 -12
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.js.map +1 -1
- package/dist/diagrams/SequenceFigure/SequenceFigure.js +35 -5
- package/dist/diagrams/SequenceFigure/SequenceFigure.js.map +1 -1
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.d.ts +3 -1
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.js +76 -28
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.js.map +1 -1
- package/dist/diagrams/index.d.ts +1 -0
- package/dist/diagrams/index.js +10 -0
- package/dist/diagrams/index.js.map +1 -1
- package/dist/diagrams/lib/dev.d.ts +61 -0
- package/dist/diagrams/lib/dev.js +96 -0
- package/dist/diagrams/lib/dev.js.map +1 -0
- package/dist/diagrams/lib/frame.d.ts +48 -7
- package/dist/diagrams/lib/frame.js +17 -2
- package/dist/diagrams/lib/frame.js.map +1 -1
- package/dist/diagrams/lib/wires.js.map +1 -1
- package/dist/diagrams/spec.d.ts +64 -12
- package/dist/index.d.ts +16 -4
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/keyframes.css +57 -3
- package/dist/lib/overlay-container.d.ts +14 -4
- package/dist/lib/overlay-container.js.map +1 -1
- package/dist/lib/warn.d.ts +73 -0
- package/dist/lib/warn.js +38 -0
- package/dist/lib/warn.js.map +1 -0
- package/dist/semantic.css +17 -6
- package/dist/styles.css +1 -1
- package/dist/tokens.css +20 -2
- package/dist/tokens.d.ts +1 -1
- package/dist/tokens.js +39 -36
- package/dist/tokens.json +39 -36
- package/llms.txt +35 -0
- package/package.json +5 -2
- package/skills/misoto22-design/evals/evals.json +84 -0
- package/skills/misoto22-design/rules/a11y.md +55 -3
- package/skills/misoto22-design/rules/forms.md +5 -0
package/dist/agent/Spinner.md
CHANGED
|
@@ -4,7 +4,7 @@ The one “working” indicator — a ring, never a shimmer.
|
|
|
4
4
|
|
|
5
5
|
- Group: Feedback
|
|
6
6
|
- Import: `import { Spinner } from '@misoto22/design'`
|
|
7
|
-
- Version: 0.
|
|
7
|
+
- Version: 0.8.0
|
|
8
8
|
- Docs: https://ui.misoto22.com/components/spinner/
|
|
9
9
|
- Related: skeleton, progress
|
|
10
10
|
|
|
@@ -12,6 +12,27 @@ The one “working” indicator — a ring, never a shimmer.
|
|
|
12
12
|
|
|
13
13
|
A wait short enough that the shape of what is coming does not matter. Longer than that, use a Skeleton.
|
|
14
14
|
|
|
15
|
+
## Anatomy
|
|
16
|
+
|
|
17
|
+
- **Live region** (required) — The outer span, and the only part with a voice. It carries role="status" while there is a label; pass label={null} and it turns into an aria-hidden box with no role at all.
|
|
18
|
+
- **Ring** (required) — The inner span — 14px, 18px or 26px of border on a transparent box, and the only element size, tone and className reach.
|
|
19
|
+
- **Leading quarter** (required) — border-t, drawn in --ink at the default tone and in the inherited colour at current. It is the whole difference between a ring and a plain circle, which is why a still ring still reads as unfinished.
|
|
20
|
+
- **Screen-reader label** — An sr-only span holding label, present unless label is null. Announced once, when the spinner mounts, and never again.
|
|
21
|
+
|
|
22
|
+
## Best practices
|
|
23
|
+
|
|
24
|
+
### Do
|
|
25
|
+
|
|
26
|
+
- Pass tone="current" for a spinner on any filled ground: the default draws the leading quarter in --ink over a --rule-2 track, and inside a primary Button both of those are the ground it is sitting on.
|
|
27
|
+
- Announce the arrival somewhere else. The label is read once on mount and nothing is said on the way out, so a reader who heard “Loading projects” is never told the projects came.
|
|
28
|
+
- Reach for label={null} only inside a control that already names the operation — it hides the whole element from assistive tech rather than merely dropping the text, so a silenced spinner standing on its own is a wait nobody is told about.
|
|
29
|
+
|
|
30
|
+
### Don’t
|
|
31
|
+
|
|
32
|
+
- label defaults to the bare word “Loading”, so a Spinner written without the prop ships the exact announcement the prop exists to prevent — the default is a placeholder, not a value.
|
|
33
|
+
- className is merged onto the ring AFTER size and tone, so it beats both: <Spinner size="lg" className="size-4" /> is a 16px ring, and the prop whose whole job was naming the size is the one that lost.
|
|
34
|
+
- Nothing here sets aria-busy — Button does that for its own control — so a spinner laid over a panel leaves the panel announced as ready while its contents are stale and its buttons still take clicks.
|
|
35
|
+
|
|
15
36
|
## Accessibility
|
|
16
37
|
|
|
17
38
|
- label names the specific thing being waited on; three spinners all saying “Loading” tell a screen reader nothing.
|
|
@@ -20,12 +41,13 @@ A wait short enough that the shape of what is coming does not matter. Longer tha
|
|
|
20
41
|
|
|
21
42
|
## Spinner
|
|
22
43
|
|
|
23
|
-
The system's one "working" indicator. A ring, not a blurred glow and not a shimmer: the White Reset has no light source, so depth and softness are not available to it. The leading quarter is the only thing that distinguishes the ring from a plain circle, which is why the track stays a hairline — a spinner is a hint that time is passing, not a feature of the page. It spins under `motion-safe` only. A reader who has asked for less motion gets a static ring, which still reads as "not finished" because the leading quarter is darker than the track. For a wait long enough that the reader would otherwise wonder whether the page is broken, prefer `Skeleton` — a shape that describes what is coming beats a dot that describes nothing.
|
|
44
|
+
The system's one "working" indicator. A ring, not a blurred glow and not a shimmer: the White Reset has no light source, so depth and softness are not available to it. The leading quarter is the only thing that distinguishes the ring from a plain circle, which is why the track stays a hairline — a spinner is a hint that time is passing, not a feature of the page. It spins under `motion-safe` only. A reader who has asked for less motion gets a static ring, which still reads as "not finished" because the leading quarter is darker than the track. For a wait long enough that the reader would otherwise wonder whether the page is broken, prefer `Skeleton` — a shape that describes what is coming beats a dot that describes nothing. `className` reaches the RING, alongside `size` and `tone`, and overrides them: every utility a caller has for a spinner is about the ring, and merged onto the wrapper instead `className="size-8"` grew an invisible box around an unchanged 18px circle. Layout still works from there — the wrapper is `inline-flex` and takes the ring's margin box as its own.
|
|
24
45
|
|
|
25
46
|
### Props
|
|
26
47
|
|
|
27
48
|
- `size` — `'sm' | 'md' | 'lg'` default `'md'`. 14px / 18px / 26px. Match the size of the type it sits beside.
|
|
28
49
|
- `tone` — `'default' | 'current'` default `'default'`. `default` draws the ring against the page — a hairline track in the rule colour with the leading quarter in ink. `current` draws it in the inherited text colour, which is what a spinner inside a filled button needs: on an ink ground an ink ring is invisible.
|
|
50
|
+
- `className` — `string`. Merged onto the RING, after `size` and `tone`, so it overrides both.
|
|
29
51
|
- `label` — `string | null` default `'Loading'`. Announced to assistive tech. Pass the specific thing being waited on ("Loading projects"), not the generic word — a screen reader user hearing "Loading" three times cannot tell which three things. Pass `null` for a spinner that sits inside a control which already names the operation (a button whose own label changes to "Saving…"), so the two are not read out twice.
|
|
30
52
|
|
|
31
53
|
Also accepts: `HTMLAttributes<HTMLSpanElement>`.
|
package/dist/agent/StatusDot.md
CHANGED
|
@@ -4,10 +4,29 @@ The dot beside a status word.
|
|
|
4
4
|
|
|
5
5
|
- Group: Display
|
|
6
6
|
- Import: `import { StatusDot } from '@misoto22/design'`
|
|
7
|
-
- Version: 0.
|
|
7
|
+
- Version: 0.8.0
|
|
8
8
|
- Docs: https://ui.misoto22.com/components/status-dot/
|
|
9
9
|
- Related: status-pill
|
|
10
10
|
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
- **Box** (required) — The aria-hidden <span> the whole thing lives in — 8px square at md, 7px at sm, inline-grid and shrink-0 so it stays circular in a flex row however long the label beside it runs.
|
|
14
|
+
- **Dot** (required) — An absolutely-positioned filled circle in --ok, --warn, --danger or --ink-3-aa. It is the only part of this component tone touches.
|
|
15
|
+
- **Halo** — A second ring of the same colour on the m22-halo keyframes, present only while pulse is true. A separate element rather than a box-shadow, because a shadow in this system is never blurred.
|
|
16
|
+
|
|
17
|
+
## Best practices
|
|
18
|
+
|
|
19
|
+
### Do
|
|
20
|
+
|
|
21
|
+
- Set pulse={false} for anything settled. It defaults to true, so a dot for a build that finished or a status that will not change today carries a halo announcing that something is happening right now.
|
|
22
|
+
- Reach for StatusPill the moment you find yourself writing the dot and its label together — that pairing assembled per call site is how one site ended up with three dot sizes and two pulse timings for the same state.
|
|
23
|
+
- Let it sit directly in the flex row beside its label: shrink-0 is what keeps it round, and a dot wrapped in a div that can shrink comes out an ellipse as soon as the label runs long.
|
|
24
|
+
|
|
25
|
+
### Don’t
|
|
26
|
+
|
|
27
|
+
- An aria-label on the dot buys nothing — aria-hidden is still set, and a hidden element has no name to give. A call site with no visible label is a state no screen reader ever reports.
|
|
28
|
+
- sm and md are 7px and 8px, one pixel apart. That is an optical adjustment for sitting beside smaller type, not a size scale, and nothing in a layout should be built on the difference.
|
|
29
|
+
|
|
11
30
|
## Accessibility
|
|
12
31
|
|
|
13
32
|
- aria-hidden without exception: it repeats a state the adjacent label already names.
|
package/dist/agent/StatusPill.md
CHANGED
|
@@ -4,13 +4,39 @@ A live state, named: a dot plus an uppercase mono label.
|
|
|
4
4
|
|
|
5
5
|
- Group: Display
|
|
6
6
|
- Import: `import { StatusPill } from '@misoto22/design'`
|
|
7
|
-
- Version: 0.
|
|
7
|
+
- Version: 0.8.0
|
|
8
8
|
- Docs: https://ui.misoto22.com/components/status-pill/
|
|
9
9
|
- Related: status-dot, badge
|
|
10
10
|
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
- **Pill** (required) — The outlined <span>: --radius-pill, a --rule-2 hairline, --paper ground, and deliberately uneven padding — 10px before the dot, 12px after the label — so the pair sits optically centred rather than mathematically.
|
|
14
|
+
- **Dot** (required) — A StatusDot handed tone and pulse. It is aria-hidden, so it is not where the tone reaches a reader who cannot see it.
|
|
15
|
+
- **Label** (required) — children in the eyebrow idiom — 11px uppercase mono, tracking pulled back from 0.2em to 0.12em because a pill is a shorter run than a section kicker.
|
|
16
|
+
- **Severity** — A visually-hidden “Warning” or “Error” at the warning and danger tones, so those two reach a reader through something other than a colour on a hidden dot. success and neutral add nothing: they are the absence of alarm, which is what a reader already assumes.
|
|
17
|
+
|
|
18
|
+
## Best practices
|
|
19
|
+
|
|
20
|
+
### Do
|
|
21
|
+
|
|
22
|
+
- Put the state in the words anyway. The severity word reaches a screen reader and nothing else — the pill’s own text is --ink-2 at every tone, so on a monochrome screen “Degraded” in a warning pill and in a neutral one are still the same pill.
|
|
23
|
+
- Set pulse={false} once the state has settled — the default halo means “right now”, and an archived or shipped pill pulsing forever tells the reader something is live when nothing is.
|
|
24
|
+
- Take the whole pill rather than composing a dot and a span at the call site: that assembly is how the same “available for work” chip came out at three dot sizes and two pulse timings on one site.
|
|
25
|
+
|
|
26
|
+
### Don’t
|
|
27
|
+
|
|
28
|
+
- It is not a live region. The pill is a plain <span>, so a state flipping from Available to Degraded while the reader is on the page changes silently — if the change is the news, the call site owns the role="status" around it.
|
|
29
|
+
- One per view, not one per row. The label is an uppercase eyebrow at 0.12em tracking — the loudest small type the system has — and a column of them down a table is Badge’s job, which is why Badge carries the same status tones in plain 12px mono.
|
|
30
|
+
|
|
31
|
+
## Accessibility
|
|
32
|
+
|
|
33
|
+
- The warning and danger tones are doubled by a visually-hidden severity word, because the dot that carries the colour is aria-hidden and has no name to give.
|
|
34
|
+
- success and neutral are silent on purpose: announcing “OK” before every settled pill is noise charged to the two tones worth interrupting for.
|
|
35
|
+
- Not a live region. A state that flips while the reader is on the page changes silently unless the call site owns a role="status" around it.
|
|
36
|
+
|
|
11
37
|
## StatusPill
|
|
12
38
|
|
|
13
|
-
A live state, named: a dot plus an uppercase mono label in an outlined pill. One component rather than a dot and a pill assembled per call site, which is how the same "available for work" chip ended up with three different dot sizes and two different pulse timings on one site.
|
|
39
|
+
A live state, named: a dot plus an uppercase mono label in an outlined pill. One component rather than a dot and a pill assembled per call site, which is how the same "available for work" chip ended up with three different dot sizes and two different pulse timings on one site. A warning or danger tone is doubled by a visually-hidden severity word, so the tone survives the dot being hidden. It does not survive monochrome: the pill's own text is `--ink-2` at every tone, and the state itself still belongs in the words the call site writes.
|
|
14
40
|
|
|
15
41
|
### Props
|
|
16
42
|
|
package/dist/agent/Steps.md
CHANGED
|
@@ -4,7 +4,7 @@ A numbered sequence, as a rail — one thing after another, with a rule through
|
|
|
4
4
|
|
|
5
5
|
- Group: Display
|
|
6
6
|
- Import: `import { Steps } from '@misoto22/design'`
|
|
7
|
-
- Version: 0.
|
|
7
|
+
- Version: 0.8.0
|
|
8
8
|
- Docs: https://ui.misoto22.com/components/steps/
|
|
9
9
|
- Related: diagram, article
|
|
10
10
|
|
|
@@ -12,6 +12,29 @@ A numbered sequence, as a rail — one thing after another, with a rule through
|
|
|
12
12
|
|
|
13
13
|
A pipeline, a migration, a recipe: an order with no branches. The moment something forks or points at something else it is a Diagram, and drawing a fork as a list hides it.
|
|
14
14
|
|
|
15
|
+
## Anatomy
|
|
16
|
+
|
|
17
|
+
- **Rail** (required) — The <ol>, carrying --step-size — one number that three rules read: the marker’s own box, where the connector starts, and where it is centred.
|
|
18
|
+
- **Marker** (required) — A 2rem circle per step, aria-hidden: the position number, or a blank hairline node when marker is "rule". Filled with --accent for the current one.
|
|
19
|
+
- **Connector** — A hairline drawn on every item but the last, placed absolutely from the foot of that marker to the foot of its row — so it spans the gap rather than running behind the markers, and no tail hangs off the end of the sequence.
|
|
20
|
+
- **Title** (required) — step.title, at 15px in the interface face. The step’s name — a noun, not a description of what happens in it.
|
|
21
|
+
- **Note** — step.note, a mono line under the title: what the step is made of, what it costs, what it uses.
|
|
22
|
+
|
|
23
|
+
## Best practices
|
|
24
|
+
|
|
25
|
+
### Do
|
|
26
|
+
|
|
27
|
+
- Use marker="rule" for a sequence of states. “Queued, running, done” is an order rather than an instruction list, and a digit in front of each one tells the reader they are steps to perform.
|
|
28
|
+
- Mark at most one step current: it is the one thing the rail states rather than draws, and two filled markers put the process in two places at once.
|
|
29
|
+
- Pass label when no heading names the sequence — it is the list’s only name, so without it the rail is announced as five items belonging to nothing.
|
|
30
|
+
- Retune the rail through --step-size on the list rather than by restyling the marker: the marker box, the connector’s start and its centring all read that one number, and moving one of the three leaves the line beginning in mid-air.
|
|
31
|
+
|
|
32
|
+
### Don’t
|
|
33
|
+
|
|
34
|
+
- A fork drawn here is a fork the reader never sees. The connector runs from each step to the next one in the array and to nothing else, so two branches flatten into four consecutive steps and the choice between them leaves the figure entirely.
|
|
35
|
+
- steps={[]} renders null rather than an empty rail, so a list filtered down to nothing leaves a heading standing over a gap unless the call site checks the length itself.
|
|
36
|
+
- Nothing here is clickable and the props spread onto the <ol>, so an onClick meant for a step lands on the whole list. A sequence the reader is meant to move through is a Breadcrumb or a Tabs.
|
|
37
|
+
|
|
15
38
|
## Accessibility
|
|
16
39
|
|
|
17
40
|
- An <ol>, because the order IS the content — a stack of divs says nothing about sequence.
|
package/dist/agent/Switch.md
CHANGED
|
@@ -4,7 +4,7 @@ A setting that takes effect immediately.
|
|
|
4
4
|
|
|
5
5
|
- Group: Forms
|
|
6
6
|
- Import: `import { Switch } from '@misoto22/design'`
|
|
7
|
-
- Version: 0.
|
|
7
|
+
- Version: 0.8.0
|
|
8
8
|
- Docs: https://ui.misoto22.com/components/switch/
|
|
9
9
|
- Related: checkbox
|
|
10
10
|
|
|
@@ -12,6 +12,26 @@ A setting that takes effect immediately.
|
|
|
12
12
|
|
|
13
13
|
Inside a form with a Save button, a switch is a lie about when the change happened. Use a Checkbox.
|
|
14
14
|
|
|
15
|
+
## Anatomy
|
|
16
|
+
|
|
17
|
+
- **Track** (required) — A 36×20 <button role="switch"> on a pill radius, filled --stone when off and --accent when on. Filled rather than outlined, so it still reads as a control on a white page.
|
|
18
|
+
- **Thumb** (required) — A 14px paper circle with a hairline, not a white disc on a shadow — this system has no shadows. It stretches to 20px while pressed and rounds out as it lands; motion-reduce drops that entirely.
|
|
19
|
+
- **Label** — Not rendered here either. The Radix root IS a <button>, though, which a <label for> does bind to — so unlike Select or RadioGroup, a Field’s label above a Switch really does click through to it.
|
|
20
|
+
|
|
21
|
+
## Best practices
|
|
22
|
+
|
|
23
|
+
### Do
|
|
24
|
+
|
|
25
|
+
- Handle the failure at the control. The flip has already claimed the change happened, so an onCheckedChange whose request fails must put the thumb back and say why, or the page is showing a setting the server does not have.
|
|
26
|
+
- Name it for the state, not the action: the accessible name is read together with “on” or “off”, so “Email notifications, on” is a sentence and “Turn on email notifications, on” is two contradictory ones.
|
|
27
|
+
- Keep the flip instant to the eye even when the write is not — an optimistic thumb with a quiet undo beats a spinner on a control whose whole claim is that it already took effect.
|
|
28
|
+
|
|
29
|
+
### Don’t
|
|
30
|
+
|
|
31
|
+
- A switch has two states and no third. “Inherit from the workspace” cannot be a switch, because the only way to draw it is unchecked, which announces “off” — that is a RadioGroup or a Select.
|
|
32
|
+
- There is no readOnly: disabled is the only lock, and it takes the control out of the tab order, so a keyboard reader tabbing the form passes the setting without ever hearing its value.
|
|
33
|
+
- Do not add transition-all from a call site. It replaces transition-[transform,width] wholesale, which puts the track’s colour on the thumb’s longer duration and turns a flip into a fade.
|
|
34
|
+
|
|
15
35
|
## Keyboard
|
|
16
36
|
|
|
17
37
|
- Space / Enter — Toggles it, and the change applies immediately.
|
package/dist/agent/Table.md
CHANGED
|
@@ -4,7 +4,7 @@ A ruled data table — alignment, sorting and rules all per column.
|
|
|
4
4
|
|
|
5
5
|
- Group: Data
|
|
6
6
|
- Import: `import { Table } from '@misoto22/design'`
|
|
7
|
-
- Version: 0.
|
|
7
|
+
- Version: 0.8.0
|
|
8
8
|
- Docs: https://ui.misoto22.com/components/table/
|
|
9
9
|
- Related: card, figure-band
|
|
10
10
|
|
|
@@ -12,6 +12,28 @@ A ruled data table — alignment, sorting and rules all per column.
|
|
|
12
12
|
|
|
13
13
|
Alignment is per column and numbers belong at the end edge, so digits line up. Sorting is opt-in per column: a table where every header is a button invites sorting a column the data cannot be ordered by.
|
|
14
14
|
|
|
15
|
+
## Anatomy
|
|
16
|
+
|
|
17
|
+
- **Scroll region** (required) — The focusable <div role="region"> around the table, named by caption. It carries the border setting and the density attribute, and it is what scrolls sideways — so the table exceeds the measure and the page does not. It is also positioned, which is what keeps an sr-only label inside a cell from resolving against the document and dragging the page sideways with it.
|
|
18
|
+
- **Caption** (required) — A real <caption>, visually hidden unless showCaption prints it as an eyebrow above the table. The same string is the scroll region’s accessible name, so it is heard on the way in and again from the table.
|
|
19
|
+
- **Column label** (required) — TH — mono and uppercase so it never reads as data, aligned per column, and always emitting scope="col" unless a call site overrides it.
|
|
20
|
+
- **Sort control** — On a sortable header only: a <button> INSIDE the th, with ArrowUp, ArrowDown or a dimmed ChevronsUpDown beside the label, and aria-sort on the th set from sortDirection.
|
|
21
|
+
- **Cells** (required) — TD — top-aligned at --ink-2, sharing --table-pad-x with the header so the columns line up, and taking their row height from --table-pad-y, which density halves from 14px to 8px.
|
|
22
|
+
|
|
23
|
+
## Best practices
|
|
24
|
+
|
|
25
|
+
### Do
|
|
26
|
+
|
|
27
|
+
- Bound the height from outside for stickyHeader: className and every other prop land on the <table>, not on the scrolling div around it, so only a constraining parent — a flex column with a height — gives that div something to stick within. A max-height on a plain wrapper does not, and the header simply travels with the page.
|
|
28
|
+
- Pass scope="row" on a row’s first cell — TH writes scope="col" and your props are spread after it, so the override lands; without it every row header claims to head a column and a cell traced back reaches the wrong label.
|
|
29
|
+
- Reset the other columns to sortDirection="none" when the sort moves: each header carries its own aria-sort and nothing coordinates them, so a table can end up announcing two columns as sorted at once.
|
|
30
|
+
- Give TD the same align as its TH — alignment is per cell, not inherited down the column, and end-aligned numbers under a start-aligned label is a column whose digits stop lining up with their own heading.
|
|
31
|
+
|
|
32
|
+
### Don’t
|
|
33
|
+
|
|
34
|
+
- aria-sort tells a reader how the table is ordered once they reach the header; it announces nothing at the moment the button is pressed. A table that re-orders under a screen reader has to say so somewhere the reader already is, or every row silently changes and nothing is said.
|
|
35
|
+
- Do not expect it to reflow on a phone: nothing stacks, the region scrolls sideways behind a hairline scrollbar, and a column past the fold is reachable only by a reader who works out that it scrolls. Eight columns at 375px wants a different presentation, not a smaller font.
|
|
36
|
+
|
|
15
37
|
## Accessibility
|
|
16
38
|
|
|
17
39
|
- caption is required: an unnamed table on a page with three tables is unnavigable.
|
|
@@ -19,6 +41,7 @@ Alignment is per column and numbers belong at the end edge, so digits line up. S
|
|
|
19
41
|
- A sortable header is a button INSIDE the th, not a click handler on the cell — a cell with an onClick is not focusable and not announced, so the sort would exist only for a mouse.
|
|
20
42
|
- aria-sort is set from sortDirection, which is the only way a screen reader learns the table is ordered at all.
|
|
21
43
|
- No zebra striping at any border setting: in a monochrome system a striped row is a second surface competing with the page ground.
|
|
44
|
+
- The scroll region is a containing block, so a visually-hidden label in a cell stays inside the table rather than escaping it and widening the page.
|
|
22
45
|
|
|
23
46
|
## Keyboard
|
|
24
47
|
|
|
@@ -27,14 +50,14 @@ Alignment is per column and numbers belong at the end edge, so digits line up. S
|
|
|
27
50
|
|
|
28
51
|
## Table
|
|
29
52
|
|
|
30
|
-
A ruled data table. Wrapped in its own horizontally scrolling container, because a table is the one block that legitimately exceeds the measure — and a page that scrolls sideways as a whole is a layout bug, while a table that does is a table. That container is focusable, and it has to be. A scrollable region whose contents are not themselves focusable is unreachable by keyboard: there is no element to Tab to and therefore no way to press an arrow key at it, so the columns past the fold simply do not exist for anyone not using a mouse. No zebra striping at any border setting. In a monochrome system a striped row is a second surface competing with the page ground, and the hairline between rows is already enough to track a line across.
|
|
53
|
+
A ruled data table. Wrapped in its own horizontally scrolling container, because a table is the one block that legitimately exceeds the measure — and a page that scrolls sideways as a whole is a layout bug, while a table that does is a table. That container is focusable, and it has to be. A scrollable region whose contents are not themselves focusable is unreachable by keyboard: there is no element to Tab to and therefore no way to press an arrow key at it, so the columns past the fold simply do not exist for anyone not using a mouse. It is also POSITIONED, which is not decoration. `sr-only` is `position: absolute`, so a visually-hidden label in a cell resolves against the nearest positioned ancestor — and with none between it and the document, it escapes the scroll container and every `overflow-hidden` around it, and widens the page by however far the table happens to be scrolled. No zebra striping at any border setting. In a monochrome system a striped row is a second surface competing with the page ground, and the hairline between rows is already enough to track a line across.
|
|
31
54
|
|
|
32
55
|
### Props
|
|
33
56
|
|
|
34
57
|
- `caption` (required) — `string`. Describes the table for a screen reader, which cannot see the heading above it. Rendered as a visually-hidden `<caption>` — required, because an unnamed table in a page with three tables is unnavigable.
|
|
35
58
|
- `showCaption` — `boolean` default `false`. Prints the caption instead of hiding it.
|
|
36
59
|
- `stickyHeader` — `boolean` default `false`. Pins the header row while the body scrolls. Needs a bounded height on the container — otherwise the page scrolls, not the table, and nothing sticks.
|
|
37
|
-
- `borders` — `TableBorders` default `'rows'`. Which rules to draw. See .
|
|
60
|
+
- `borders` — `TableBorders` default `'rows'`. Which rules to draw. See TableBorders.
|
|
38
61
|
- `density` — `'comfortable' | 'compact'` default `'comfortable'`. Tightens the row padding, for a table that is mostly numbers.
|
|
39
62
|
|
|
40
63
|
Also accepts: `TableHTMLAttributes<HTMLTableElement>`.
|
package/dist/agent/Tabs.md
CHANGED
|
@@ -4,10 +4,32 @@ One strip, several panels.
|
|
|
4
4
|
|
|
5
5
|
- Group: Navigation
|
|
6
6
|
- Import: `import { Tabs } from '@misoto22/design'`
|
|
7
|
-
- Version: 0.
|
|
7
|
+
- Version: 0.8.0
|
|
8
8
|
- Docs: https://ui.misoto22.com/components/tabs/
|
|
9
9
|
- Related: accordion
|
|
10
10
|
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
- **Root** (required) — Tabs — Radix’s root, re-exported. It draws nothing and owns everything: value or defaultValue, and activationMode. With neither value nor defaultValue no tab is selected and no panel is mounted.
|
|
14
|
+
- **Tab strip** (required) — TabsList — the role="tablist" row, sitting on one hairline rule and scrolling on its own axis with scroll-slim. It has no accessible name unless you give it one.
|
|
15
|
+
- **Tab** (required) — TabsTrigger, at the md control height with its label on one line. Every tab carries the 2px active marker, transparent until it is the selected one; the marker is pulled onto the strip’s own border with -mb-px so the two share a line rather than stacking into a 3px edge.
|
|
16
|
+
- **Panel** — TabsContent, paired to its tab by matching value. Mounted only while it is the selected one, and marked data-m22-animated so its entrance is dropped for a reader who asked for less motion.
|
|
17
|
+
|
|
18
|
+
## Best practices
|
|
19
|
+
|
|
20
|
+
### Do
|
|
21
|
+
|
|
22
|
+
- Give the root a defaultValue or a value: with neither, nothing matches, every panel stays unmounted, and the page renders a strip above an empty space with nothing to say what is missing.
|
|
23
|
+
- Match each trigger’s value to a panel’s value exactly — the pairing is string equality, and a typo is not an error but a tab that opens onto nothing.
|
|
24
|
+
- Pass activationMode="manual" when a panel fetches or renders something expensive: the default is automatic, so ← and → select as they move and arrowing across four tabs starts four loads before the reader has stopped.
|
|
25
|
+
- Give TabsList an aria-label on a page with more than one set: Radix names the tablist after nothing, and two unnamed tablists are two “tab list”s a reader cannot tell apart.
|
|
26
|
+
|
|
27
|
+
### Don’t
|
|
28
|
+
|
|
29
|
+
- An unselected panel is unmounted, not hidden — find-in-page cannot reach its text, a print takes only the panel that was open, and a half-filled form in another tab has lost what was typed into it by the time the reader comes back.
|
|
30
|
+
- The selected tab lives in React state, not in the URL: a reader who reloads or shares the page lands on the first panel, so anything worth linking to needs value lifted into a query parameter.
|
|
31
|
+
- Tabs are not a way to fit more in: the strip scrolls silently, and a sixth tab past the fold looks exactly like a page that only has five.
|
|
32
|
+
|
|
11
33
|
## Accessibility
|
|
12
34
|
|
|
13
35
|
- The strip scrolls rather than wrapping: a wrapped second row moves every tab below it and the reader loses the one they were about to click.
|
|
@@ -33,7 +55,7 @@ One tab. The active marker is a 2px ink rule pulled onto the strip's own border
|
|
|
33
55
|
|
|
34
56
|
## TabsContent
|
|
35
57
|
|
|
36
|
-
The panel paired to a
|
|
58
|
+
The panel paired to a TabsTrigger by matching `value`.
|
|
37
59
|
|
|
38
60
|
## Examples
|
|
39
61
|
|
package/dist/agent/Tag.md
CHANGED
|
@@ -1,35 +1,66 @@
|
|
|
1
1
|
# Tag
|
|
2
2
|
|
|
3
|
-
A subject label — a topic, a technology, a filter facet.
|
|
3
|
+
A subject label — a topic, a technology, a filter facet — that filters with onClick and is dismissed with onRemove.
|
|
4
4
|
|
|
5
5
|
- Group: Display
|
|
6
6
|
- Import: `import { Tag } from '@misoto22/design'`
|
|
7
|
-
- Version: 0.
|
|
7
|
+
- Version: 0.8.0
|
|
8
8
|
- Docs: https://ui.misoto22.com/components/tag/
|
|
9
|
-
- Related: badge
|
|
9
|
+
- Related: badge, status-pill
|
|
10
10
|
|
|
11
11
|
## When to reach for it
|
|
12
12
|
|
|
13
|
-
Several sit together and the reader scans them. One fact about one record is a Badge.
|
|
13
|
+
Several sit together and the reader scans them. One fact about one record is a Badge. A chip the reader can toggle or dismiss is this one with onClick or onRemove, not a fourth component.
|
|
14
|
+
|
|
15
|
+
## Anatomy
|
|
16
|
+
|
|
17
|
+
- **Chip** (required) — The <span>: the same --radius-sm corner, the same 10px by 4px padding and the same 12px mono as a Badge, with no border of its own.
|
|
18
|
+
- **Ground** (required) — --stone at rest, --accent once active, cross-fading over --duration-fast. The accent is the system’s one pointer at a choice, which is why it is what selection is drawn in.
|
|
19
|
+
- **Label** (required) — children, in --ink-3-aa — the AA floor rather than a light grey — and in --accent-foreground once active.
|
|
20
|
+
- **Remove button** — On onRemove only: a real <button type="button"> after the label, holding a 12px X and named by removeLabel. The drawn box is 16px, under the 24px WCAG 2.5.8 floor, so an inset pseudo-element takes the hit area out to 24 without changing the drawing or pushing the chips apart.
|
|
21
|
+
- **Filter control** — On onClick only: the chip itself becomes the <button>, carrying aria-pressed from active. Given onRemove as well, the label splits into its own button beside the X — two siblings, never one inside the other — and takes the leading padding with it so the target is the chip up to the X rather than just the words.
|
|
22
|
+
|
|
23
|
+
## Best practices
|
|
24
|
+
|
|
25
|
+
### Do
|
|
26
|
+
|
|
27
|
+
- Filter with onClick, not with a wrapper. The chip becomes the button itself, so the padding is part of the target and the focus ring is drawn around what the reader sees — and a removable chip does not end up with a button inside a button.
|
|
28
|
+
- Pass active on a chip that toggles, and leave it off one that does not. aria-pressed is read off that same value, so the accent fill and the state assistive tech hears cannot drift apart; omitted, nothing is announced, which is the right answer for a chip that navigates rather than toggles.
|
|
29
|
+
- Give a filter row an off state to come back to — active is the accent, and a row in which every tag is active spends the mark that means “this one” on all of them.
|
|
30
|
+
- Name the subject in removeLabel — "Remove Rust filter", not "Remove". It is required alongside onRemove because eight chips whose controls are all called Remove is eight controls a screen reader cannot tell apart.
|
|
31
|
+
|
|
32
|
+
### Don’t
|
|
33
|
+
|
|
34
|
+
- It carries no tone at all, so a tag cannot say success or danger. Colouring one in through className puts a hue into the system by hand and leaves the accent as the only thing that still reads as selected.
|
|
35
|
+
- One tag on its own is a Badge that lost its tone. The component is built to be scanned in a row, and a single chip beside a record is one fact about one record.
|
|
36
|
+
- Do not wrap it in a button of your own. Around a removable chip that is a button inside a button — invalid markup a parser splits into siblings, leaving a DOM neither the author nor the accessibility tree expects. onClick is what that wrapper was for.
|
|
14
37
|
|
|
15
38
|
## Accessibility
|
|
16
39
|
|
|
17
|
-
- Presentational
|
|
40
|
+
- Presentational until it is given a handler. onClick makes the chip a real button carrying aria-pressed from active, so the focus ring and the pressed state stay on the element that draws them.
|
|
41
|
+
- Both controls are real <button type="button">s and siblings, so Tab reaches each, Enter and Space fire each, and neither submits the form it happens to sit in.
|
|
42
|
+
- removeLabel is required with onRemove and is the button’s whole accessible name — the X itself is aria-hidden.
|
|
18
43
|
|
|
19
44
|
## Tag
|
|
20
45
|
|
|
21
|
-
A subject label — a topic, a technology, a filter facet
|
|
46
|
+
A subject label — a topic, a technology, a filter facet — that can filter with `onClick` and be dismissed with `onRemove`. Distinct from `Badge`, which carries a state or a count. A tag names what something is ABOUT, so several sit together in a row and the reader scans them; a badge is one fact about one record. This is where a `Token` component would have gone. It was not built: a token is a tag with a remove button, and the difference between the two is one prop, not one component. The system already ships three things that look alike — `Badge`, `Tag`, `StatusPill` — and a fourth whose whole distinction is an X on the end would be the one a call site picks by coin toss. Presentational until it is given a handler, and the component owns both interactive cases rather than leaving one to a wrapper at the call site. That is not a convenience: a wrapping `<button>` around a chip that already holds the remove `<button>` is a button inside a button, which no parser keeps and no accessibility tree reports the way it was written. Given both, the chip renders the label and the X as SIBLING buttons — the label takes the leading padding with it, so it is the whole of the chip up to the X rather than the words with dead padding around them.
|
|
22
47
|
|
|
23
48
|
### Props
|
|
24
49
|
|
|
25
50
|
- `children` (required) — `ReactNode`.
|
|
26
|
-
- `active` — `boolean
|
|
51
|
+
- `active` — `boolean`. Renders the pressed/selected look, and declares the chip a toggle. An interactive chip reads `aria-pressed` off this same value, so the accent fill and the state a screen reader hears cannot drift apart. Leave it off for a chip whose `onClick` navigates or opens something: a control that is neither pressed nor unpressed is better announced as neither than as "not pressed".
|
|
52
|
+
- `onClick` — `() => void`. Makes the chip ITSELF the filter control. This prop exists because the alternative did not survive contact with `onRemove`. The advice used to be to wrap the tag in a button at the call site, which is fine on its own and invalid the moment the chip is also removable: the remove control is a real `<button>`, so the wrapper puts a button inside a button — markup the parser splits into siblings, leaving a DOM neither the author nor the accessibility tree expects. With this the wrapper is never written. The chip carries the click, the focus ring and the pressed state on the element that draws them, and a removable filter chip renders its two controls side by side instead of one inside the other.
|
|
53
|
+
- `onRemove` — `() => void`. Called when the reader dismisses the chip.
|
|
54
|
+
- `removeLabel` — `string`. The remove button's accessible name. Name the subject, not the action.
|
|
27
55
|
|
|
28
|
-
Also accepts: `HTMLAttributes<
|
|
56
|
+
Also accepts: `Omit<HTMLAttributes<HTMLElement>, 'onClick'>`.
|
|
29
57
|
|
|
30
58
|
## Examples
|
|
31
59
|
|
|
32
60
|
```tsx
|
|
33
61
|
<Tag>TypeScript</Tag>
|
|
34
|
-
<
|
|
62
|
+
<Tag active={on} onClick={toggle}>Rust</Tag>
|
|
63
|
+
<Tag onRemove={() => drop('rust')} removeLabel="Remove Rust filter">Rust</Tag>
|
|
64
|
+
// Both: a filter the reader can toggle and also take out of the row.
|
|
65
|
+
<Tag active={on} onClick={toggle} onRemove={drop} removeLabel="Remove Rust filter">Rust</Tag>
|
|
35
66
|
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Text
|
|
2
|
+
|
|
3
|
+
The system’s paragraph, on the second rung of the ink ladder.
|
|
4
|
+
|
|
5
|
+
- Group: Display
|
|
6
|
+
- Import: `import { Text } from '@misoto22/design'`
|
|
7
|
+
- Version: 0.8.0
|
|
8
|
+
- Docs: https://ui.misoto22.com/components/text/
|
|
9
|
+
- Related: heading, article, markdown
|
|
10
|
+
|
|
11
|
+
## When to reach for it
|
|
12
|
+
|
|
13
|
+
One paragraph, or one run of text, outside a reading column. A whole column of prose is an Article.
|
|
14
|
+
|
|
15
|
+
## Anatomy
|
|
16
|
+
|
|
17
|
+
- **Box** (required) — The element `as` names — a <p> unless told otherwise. It carries the size, the tone and margin: 0, so the spacing between blocks belongs to the surface rather than to the paragraph.
|
|
18
|
+
- **Type step** (required) — size, one of four: xs, sm, base and lead. lead is --fs-item, the bottom rung of the heading ladder, and is the standfirst that carries a piece.
|
|
19
|
+
- **Ink step** (required) — tone, one of three, because the ink ladder has three rungs: body is --ink-2, strong is --ink, muted is --ink-3-aa.
|
|
20
|
+
|
|
21
|
+
## Best practices
|
|
22
|
+
|
|
23
|
+
### Do
|
|
24
|
+
|
|
25
|
+
- Leave tone alone for body copy. The default is --ink-2 on purpose: a page whose paragraphs are all full-strength ink has spent the top of the ladder on its body text and has nothing left for the headings.
|
|
26
|
+
- Use as="span" for a run inside a sentence. A <p> nested inside a <p> is not nesting — the HTML parser closes the outer one and you get two paragraphs and a broken layout.
|
|
27
|
+
- Reach for size="lead" for the standfirst under a title, and stop there. It is --fs-item, the same step an in-card title uses; anything larger is a heading that has not admitted it.
|
|
28
|
+
|
|
29
|
+
### Don’t
|
|
30
|
+
|
|
31
|
+
- Do not set spacing on it. Every Text is margin: 0, so a stack of them inside a plain <div> has no rhythm by design — put them in an Article or give the container the gap, or every surface ends up with its own idea of what a paragraph gap is.
|
|
32
|
+
- tone="muted" is --ink-3-aa, never --ink-3. The two look identical on paper and are not the same token: --ink-3 is a translucent tint that takes on whatever is under it, so it clears AA on the page ground and quietly fails on a card or a code plate.
|
|
33
|
+
- Do not use it as a heading with a bigger size. The element is what a screen reader navigates by, and a <p> at --fs-item is invisible to a heading list.
|
|
34
|
+
|
|
35
|
+
## Accessibility
|
|
36
|
+
|
|
37
|
+
- as changes the element and nothing else, so the markup can say what the content is without the look changing under it.
|
|
38
|
+
- Every tone is an AA-safe rung; the muted step is --ink-3-aa rather than the translucent --ink-3.
|
|
39
|
+
|
|
40
|
+
## Text
|
|
41
|
+
|
|
42
|
+
A paragraph, or a run of text that wants the system's voice. The step between `Article` and raw JSX. `Article` styles a whole reading column from element selectors and is the right answer for a post; this is for the single paragraph that is NOT in a column — a card's description, a dialog's explanation, the line under an empty state. The default tone is `body` (`--ink-2`), not `--ink`. A page whose paragraphs are all full-strength ink has spent the top of the ladder on its body copy and has nothing left for the headings, which is the single most common way a monochrome page loses its hierarchy.
|
|
43
|
+
|
|
44
|
+
### Props
|
|
45
|
+
|
|
46
|
+
- `children` — `ReactNode`.
|
|
47
|
+
- `size` — `TextSize` default `'base'`. Which rung of the type scale. See TextSize.
|
|
48
|
+
- `tone` — `TextTone` default `'body'`. Which rung of the ink ladder. See TextTone.
|
|
49
|
+
- `as` — `TextElement` default `'p'`. The element, and ONLY the element. Changing it changes what the markup means — a `span` inside a sentence, an `li` inside a list someone else opened — and changes nothing about the look. That separation is the whole point: the alternative is a `<p>` nested inside a `<p>`, which the HTML parser silently splits into two, or a paragraph faked out of a `<div>` because the real element brought a size with it.
|
|
50
|
+
|
|
51
|
+
Also accepts: `HTMLAttributes<HTMLElement>`.
|
|
52
|
+
|
|
53
|
+
## Types
|
|
54
|
+
|
|
55
|
+
- `TextSize` = `'xs' | 'sm' | 'base' | 'lead'`
|
|
56
|
+
- `TextTone` = `'body' | 'strong' | 'muted'`
|
|
57
|
+
- `TextElement` = `'p' | 'span' | 'div' | 'li' | 'figcaption'`
|
|
58
|
+
|
|
59
|
+
## Examples
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<Text>Twelve releases this quarter, none rolled back.</Text>
|
|
63
|
+
<Text size="lead" tone="strong">A monochrome system for software and writing.</Text>
|
|
64
|
+
<Text as="span" size="sm" tone="muted">Updated just now</Text>
|
|
65
|
+
```
|
package/dist/agent/Textarea.md
CHANGED
|
@@ -4,10 +4,29 @@ Multi-line text entry, resizable vertically only.
|
|
|
4
4
|
|
|
5
5
|
- Group: Forms
|
|
6
6
|
- Import: `import { Textarea } from '@misoto22/design'`
|
|
7
|
-
- Version: 0.
|
|
7
|
+
- Version: 0.8.0
|
|
8
8
|
- Docs: https://ui.misoto22.com/components/textarea/
|
|
9
9
|
- Related: input, field
|
|
10
10
|
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
- **Control box** (required) — The <textarea>, on the same CONTROL_BASE as Input plus a min-h-24 floor. That floor is under rows, not over it: rows={2} still renders six rems tall.
|
|
14
|
+
- **Resize grip** — The browser’s own corner handle, constrained to resize-y — a reader can lengthen the box but cannot drag it past the measure or out of the page’s gutter.
|
|
15
|
+
- **Danger border** — The same pair isInvalid reads on Input: the invalid prop or an aria-invalid, including the one a Field sets when it has an error.
|
|
16
|
+
|
|
17
|
+
## Best practices
|
|
18
|
+
|
|
19
|
+
### Do
|
|
20
|
+
|
|
21
|
+
- Set rows to the answer you expect. It is the only thing that raises the resting height above the six-rem floor, and the size of the box is the clearest thing on the page about how long an answer should be.
|
|
22
|
+
- If there is a length limit, set maxLength AND say so in the hint: maxLength swallows the keystroke without explaining, and a paste one character too long is silently truncated.
|
|
23
|
+
- Keep Enter meaning newline. A textarea that submits on Enter has taken the one key the control exists to accept, and the reader loses the paragraph they were halfway through.
|
|
24
|
+
|
|
25
|
+
### Don’t
|
|
26
|
+
|
|
27
|
+
- There is no auto-grow. The height is whatever rows and min-h-24 settled on and it never follows the content, so a long answer is reviewed through a six-rem window unless the call site says otherwise.
|
|
28
|
+
- Do not put a required format in the Field’s hint and nothing else: hint and error share one slot, so the format vanishes the instant the field is wrong — which is the only moment anyone needed it.
|
|
29
|
+
|
|
11
30
|
## Textarea
|
|
12
31
|
|
|
13
32
|
Multi-line text entry. Resizes vertically only: horizontal resize lets a reader drag the control past the measure and past the page's own gutter.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Timestamp
|
|
2
|
+
|
|
3
|
+
A date or a time, rendered the one way the system renders them.
|
|
4
|
+
|
|
5
|
+
- Group: Display
|
|
6
|
+
- Import: `import { Timestamp } from '@misoto22/design'`
|
|
7
|
+
- Version: 0.8.0
|
|
8
|
+
- Docs: https://ui.misoto22.com/components/timestamp/
|
|
9
|
+
- Related: text, badge, description-list
|
|
10
|
+
|
|
11
|
+
## When to reach for it
|
|
12
|
+
|
|
13
|
+
Any instant on screen. The alternative is toLocaleString() at the call site, which is how a product ends up with four date formats on one screen.
|
|
14
|
+
|
|
15
|
+
## Anatomy
|
|
16
|
+
|
|
17
|
+
- **Element** (required) — A <time> whose datetime is the full ISO instant from the very first render and never changes, so anything parsing the markup gets the exact moment whether or not the effect has run.
|
|
18
|
+
- **First paint** (required) — The UTC calendar date, sliced straight out of the ISO string with no Intl anywhere near it. It is what the server renders and what the client renders while hydrating — both sides compute it from the same characters, so they cannot disagree.
|
|
19
|
+
- **Local reading** (required) — Applied after mount, in an effect, where there is a reader to be local to: Intl.RelativeTimeFormat with numeric "auto" for the relative form, Intl.DateTimeFormat at dateStyle medium for the absolute one.
|
|
20
|
+
- **Missing value** — A value nothing can parse renders an em dash at --ink-3-aa and no <time> at all, because an element whose datetime cannot be written is not a time.
|
|
21
|
+
|
|
22
|
+
## Best practices
|
|
23
|
+
|
|
24
|
+
### Do
|
|
25
|
+
|
|
26
|
+
- Leave format on auto for a record list. It reads relative while the gap is under relativeWithin — a week by default — and switches to the calendar date past it, which is both the more useful fact and the one that stops changing.
|
|
27
|
+
- Pass the instant, not a formatted string. A Date, an ISO string or epoch milliseconds all work, and all three end up as the same ISO datetime attribute.
|
|
28
|
+
- Re-render from above when a list has to tick. It formats once per mount on purpose: a hundred rows each holding an interval to keep "3 minutes ago" honest is a cost nobody asked for.
|
|
29
|
+
|
|
30
|
+
### Don’t
|
|
31
|
+
|
|
32
|
+
- Do not expect the relative text in the server-rendered HTML. The first paint is deliberately the UTC date — a crawler, a static export and a test reading markup all see 2026-01-14, and only a mounted browser sees "3 hours ago".
|
|
33
|
+
- Do not format a date beside it with toLocaleString. The two would disagree the moment one page renders on a build server, which is exactly the hydration mismatch this component is built around.
|
|
34
|
+
- Do not use it for a duration. It renders an instant relative to now; "2m 14s of build time" is a length, not a moment, and belongs in a plain string.
|
|
35
|
+
|
|
36
|
+
## Accessibility
|
|
37
|
+
|
|
38
|
+
- The datetime attribute carries the exact ISO instant from the first render, so assistive technology reading the machine value never depends on an effect having run.
|
|
39
|
+
- The visible text changes once after mount and the machine value never does, which keeps the announced value and the parsed value in agreement.
|
|
40
|
+
- An unparseable value renders an em dash rather than the browser’s literal "Invalid Date" string, which is an engineering artefact and not something to put in front of a reader.
|
|
41
|
+
|
|
42
|
+
## Timestamp
|
|
43
|
+
|
|
44
|
+
A date or a time, rendered the one way the system renders them. Every list of records needs this, and `new Date().toLocaleString()` at the call site is precisely how a product ends up with four date formats on one screen. **On hydration.** Both halves of a formatted date are environment-dependent: a relative time depends on when it is read, and even an absolute one depends on the reader's locale and time zone, none of which a static build knows. So the first paint — the one the server produces and the one the client must reproduce exactly — is the ISO calendar date in UTC, sliced straight out of the ISO string with no `Intl` anywhere near it. Both sides compute it from the same characters, so they cannot disagree. The locale-aware and relative forms are applied after mount, in an effect, where there is a reader to be local to. This package statically exports every page, so the alternative is a hydration mismatch on any page with a date on it. The `datetime` attribute is the full ISO instant from the first render onwards and never changes, so a screen reader, a crawler, or anything else parsing the markup gets the exact moment whether or not the effect has run. It formats once per mount. A hundred rows each holding a ticking interval to keep "3 minutes ago" honest is a cost nobody asked for; a list that must tick should re-key or re-render from above.
|
|
45
|
+
|
|
46
|
+
### Props
|
|
47
|
+
|
|
48
|
+
- `value` (required) — `Date | string | number`. The instant. A `Date`, an ISO string, or epoch milliseconds.
|
|
49
|
+
- `format` — `TimestampFormat` default `'auto'`. How the instant reads. See TimestampFormat.
|
|
50
|
+
- `relativeWithin` — `number` default `604_800_000`. How far from now `auto` still prints a relative time, in milliseconds. Seven days by default — past a week the calendar date is the more useful fact, and it is also the one that stops changing.
|
|
51
|
+
- `showTime` — `boolean` default `false`. Adds the clock time to the absolute form.
|
|
52
|
+
|
|
53
|
+
Also accepts: `Omit<TimeHTMLAttributes<HTMLTimeElement>, 'dateTime' | 'children'>`.
|
|
54
|
+
|
|
55
|
+
## Types
|
|
56
|
+
|
|
57
|
+
- `TimestampFormat` = `'auto' | 'relative' | 'absolute'`
|
|
58
|
+
|
|
59
|
+
## Examples
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<Timestamp value={deploy.finishedAt} />
|
|
63
|
+
<Timestamp value="2026-01-14T09:30:00Z" format="absolute" showTime />
|
|
64
|
+
```
|
package/dist/agent/Toast.md
CHANGED
|
@@ -4,7 +4,7 @@ A transient confirmation, mounted once near the app root.
|
|
|
4
4
|
|
|
5
5
|
- Group: Feedback
|
|
6
6
|
- Import: `import { Toast } from '@misoto22/design'`
|
|
7
|
-
- Version: 0.
|
|
7
|
+
- Version: 0.8.0
|
|
8
8
|
- Docs: https://ui.misoto22.com/components/toast/
|
|
9
9
|
- Related: alert
|
|
10
10
|
|
|
@@ -12,9 +12,33 @@ A transient confirmation, mounted once near the app root.
|
|
|
12
12
|
|
|
13
13
|
Something succeeded and needs no response. A toast is dismissed by time, and time is not an acknowledgement.
|
|
14
14
|
|
|
15
|
+
## Anatomy
|
|
16
|
+
|
|
17
|
+
- **Toaster** (required) — The single mount, bottom-right by default. It renders sonner’s list into a portal at the end of body, which is why the theming is an inline style — a stylesheet scoped to the app never reaches there.
|
|
18
|
+
- **Token style** (required) — sonner’s --normal-bg, --normal-text, --normal-border and --border-radius pointed at --paper, --ink, --rule-2 and --radius, plus the sans face. The --success-* and --error-* pairs join them only when richColors is on, which is the only state sonner reads them in.
|
|
19
|
+
- **Notification region** (required) — sonner’s own section, aria-live="polite" and labelled “Notifications altKey+KeyT”. Taken out of the tab order and reachable by that shortcut, and polite for every toast type — there is no assertive path.
|
|
20
|
+
- **Toast** — One entry, pushed by toast() or one of its typed variants and removed from the DOM after sonner’s default four seconds, which this wrapper does not change. Three are visible at once; the rest queue.
|
|
21
|
+
- **Close button** — On by default here, where sonner ships it off. Without it the only ways out are the timer and a swipe, and a keyboard has no swipe.
|
|
22
|
+
|
|
23
|
+
## Best practices
|
|
24
|
+
|
|
25
|
+
### Do
|
|
26
|
+
|
|
27
|
+
- Mount exactly one Toaster. Every toast() call reaches every Toaster listening, so one in a root layout and one in a nested layout render the same message twice, in two corners.
|
|
28
|
+
- Set duration per toast for anything longer than a short sentence. The wrapper leaves sonner’s default at four seconds — roughly ten words read aloud — and the rest of the message is removed from the page before it has been read.
|
|
29
|
+
- Keep the message to what happened. Three toasts are visible at a time and the rest wait their turn, so a loop that toasts per item shows the last three and delivers the others after the reader has moved on.
|
|
30
|
+
- Set data-mode on <html> and leave theme alone. sonner hard-codes the description’s colour per theme — #3f3f3f, overridden only under its dark theme — so a page painted dark by an attribute the Toaster was not following put that grey on --paper at roughly 1.85:1, and every toast with a description lost its second half.
|
|
31
|
+
|
|
32
|
+
### Don’t
|
|
33
|
+
|
|
34
|
+
- Nothing a reader must act on belongs here. Four seconds is a deadline they were never told about, and the button is in a portal at the end of body that a keyboard reaches last — an Undo in a toast is an offer most people cannot take.
|
|
35
|
+
- Nothing a reader must read twice belongs here either — an error code, a reference, a name to type elsewhere. There is no history: once the timer expires the text is out of the DOM and unrecoverable.
|
|
36
|
+
- Do not report a failure with toast.error and consider it reported. The region is polite for every type, so the failure queues behind whatever the screen reader was already saying and can be removed before its turn comes.
|
|
37
|
+
- Do not pass theme="system". It reads prefers-color-scheme, not data-mode, so a reader who overrode a dark operating system with a light page gets a dark toast over it — the same defect the old default had, pointing the other way.
|
|
38
|
+
|
|
15
39
|
## Toaster
|
|
16
40
|
|
|
17
|
-
Transient confirmations, mounted once near the app root. A toast is for something that succeeded and needs no response. Anything the reader must act on belongs in the page — a toast is dismissed by time, and time is not an acknowledgement. `richColors` stays off by default: sonner's rich palette is not this system's status scale, and turning it on reintroduces chroma the White Reset spends only on state.
|
|
41
|
+
Transient confirmations, mounted once near the app root. A toast is for something that succeeded and needs no response. Anything the reader must act on belongs in the page — a toast is dismissed by time, and time is not an acknowledgement. `theme` follows `data-mode`. sonner defaults to `light` and hard-codes the description's colour per theme — `#3f3f3f`, overridden only under `[data-sonner-theme=dark]` — so a Toaster that never passed `theme` put dark grey text on this system's `--paper: #0d0d0d` and every `toast(title, { description })` lost its second half. Pass `theme` yourself and that wins; sonner's own `system` is the one value not to reach for, since it reads the OS rather than the attribute the page is actually painted from. `richColors` stays off by default: sonner's rich palette is not this system's status scale, and turning it on reintroduces chroma the White Reset spends only on state.
|
|
18
42
|
|
|
19
43
|
## Examples
|
|
20
44
|
|