# misoto22 design > A monochrome design system for software, writing and photography: > portable CSS tokens and accessible React primitives. Paper ground, > near-black mark, and status is the only chroma in the file. Install: `npm install @misoto22/design`. Styles: `import '@misoto22/design/styles.css'`. Every component is a client-safe React 19 component; the token layer is plain CSS and works without React at all. ## The laws 1. **The ground is paper.** White, not off-white, and the same white on every surface. There is no card colour, no elevated panel tint, and no "subtle background" that quietly becomes a second ground. Rules out: A card that separates itself from the page by being a different shade of white. 2. **A shadow is never blurred.** The system has no light source, so it has no elevation ramp. Depth is a hairline, a change of ground, or a hard ink offset with no blur radius. Rules out: box-shadow: 0 2px 8px rgba(0,0,0,.08) — and the whole scale it belongs to. 3. **The rule does the work colour would.** Three weights, and each is chosen by what it separates: hairline between rows, edge between blocks, hard under a masthead. A monochrome page has nothing else to divide with. Rules out: Five hand-tuned greys, picked per component, that nobody can tell apart. 4. **There are two text steps and nothing lighter.** The floor clears AA on white at 6.7:1. Every step above it is darker. A third, lighter grey is not a design decision available here. Rules out: #999 on white, for "secondary" text that half the readers cannot see. 5. **Chroma is bound to state.** Green, amber and red mean succeeded, needs attention, and failed. They are never decorative, and the meaning is always doubled — by an icon, by the words, or by both. Rules out: A blue badge because the row needed some colour. 6. **One ladder, and the page owns the top of it.** Five heading steps, fluid between a phone and the full page, and nothing sits above the page title. Two headings that nest must skip a step, or the hierarchy is not readable as one. Rules out: A card title on one page that is larger than another page’s own h1. 7. **The accent is ink.** In a monochrome system the single editorial pointer collapses onto the mark. What used to be carried by hue is carried by weight, by an underline, by a filled pill, or by reversal. Rules out: A brand hue reintroduced through a link colour or a hover state. 8. **Dark mode is a value swap, not a second palette.** The same token names, different values. That is why a component reads a semantic alias and never a primitive: the alias re-resolves on its own when the mode flips. The two exceptions — type over a photograph, and a foreign brand mark — are documented where they are defined, because their ground is not the theme. Rules out: A .dark block in a component’s own stylesheet, freezing one side of the swap. ## Theming Six attributes on the root re-point tokens the package already defines. No component reads any of them, and none of them introduces a token. - `data-mode`: light | dark - `data-surface`: paper | warm | cool - `data-radius`: sharp | soft | round - `data-rules`: quiet | hairline | firm - `data-type`: editorial | grotesk | bookish - `data-motion`: still | calm | snappy - `data-density`: comfortable | compact Plus `data-accent`, which re-points the single token every chosen state reads. An unset attribute is the default; nothing is anchored to `:root`, so an axis set on any element applies to the subtree below it. ## Components - [Button](https://ui.misoto22.com/components/button/llms.txt): The system’s action, as a pill that does not move on hover. - [FloatingIconButton](https://ui.misoto22.com/components/floating-icon-button/llms.txt): A round action pinned to a screen corner. - [Badge](https://ui.misoto22.com/components/badge/llms.txt): A count or a state, set in mono so it reads as metadata. - [Tag](https://ui.misoto22.com/components/tag/llms.txt): A subject label — a topic, a technology, a filter facet. - [Kbd](https://ui.misoto22.com/components/kbd/llms.txt): A key on a keyboard, set as one. - [Avatar](https://ui.misoto22.com/components/avatar/llms.txt): A person, as a circle, with initials until the image lands. - [StatusDot](https://ui.misoto22.com/components/status-dot/llms.txt): The dot beside a status word. - [StatusPill](https://ui.misoto22.com/components/status-pill/llms.txt): A live state, named: a dot plus an uppercase mono label. - [LinkArrow](https://ui.misoto22.com/components/link-arrow/llms.txt): The mark on a link that leaves the page. - [Separator](https://ui.misoto22.com/components/separator/llms.txt): A rule, in the three weights a monochrome page needs. - [FigureBand](https://ui.misoto22.com/components/figure-band/llms.txt): A row of counted facts, divided by hairlines and nothing else. - [Spinner](https://ui.misoto22.com/components/spinner/llms.txt): The one “working” indicator — a ring, never a shimmer. - [Skeleton](https://ui.misoto22.com/components/skeleton/llms.txt): The shape of the page, before the page. - [Progress](https://ui.misoto22.com/components/progress/llms.txt): A bar that fills, or sweeps when the end is unknown. - [Alert](https://ui.misoto22.com/components/alert/llms.txt): A message about the page, in place. - [EmptyState](https://ui.misoto22.com/components/empty-state/llms.txt): A collection with nothing in it — yet. - [ErrorState](https://ui.misoto22.com/components/error-state/llms.txt): A page that could not be shown. - [Toast](https://ui.misoto22.com/components/toast/llms.txt): A transient confirmation, mounted once near the app root. - [Field](https://ui.misoto22.com/components/field/llms.txt): A labelled form row: label, control, and the one message below it. - [Input](https://ui.misoto22.com/components/input/llms.txt): A single line of text entry. - [Textarea](https://ui.misoto22.com/components/textarea/llms.txt): Multi-line text entry, resizable vertically only. - [Select](https://ui.misoto22.com/components/select/llms.txt): A choice from a list, styled the whole way down. - [NativeSelect](https://ui.misoto22.com/components/native-select/llms.txt): The platform’s own picker, restyled where it can be. - [Checkbox](https://ui.misoto22.com/components/checkbox/llms.txt): A choice that takes effect when the form is submitted. - [RadioGroup](https://ui.misoto22.com/components/radio-group/llms.txt): A set of mutually exclusive choices. - [Switch](https://ui.misoto22.com/components/switch/llms.txt): A setting that takes effect immediately. - [Dialog](https://ui.misoto22.com/components/dialog/llms.txt): A modal surface: portal, scrim, centred panel. - [DropdownMenu](https://ui.misoto22.com/components/dropdown-menu/llms.txt): A menu of actions. - [Tooltip](https://ui.misoto22.com/components/tooltip/llms.txt): A short label on hover and on focus. - [Tabs](https://ui.misoto22.com/components/tabs/llms.txt): One strip, several panels. - [Accordion](https://ui.misoto22.com/components/accordion/llms.txt): Disclosure rows that open in place. - [Breadcrumb](https://ui.misoto22.com/components/breadcrumb/llms.txt): Where you are, as a path. - [Pagination](https://ui.misoto22.com/components/pagination/llms.txt): Numbered pages, with the middle elided. - [NavItem](https://ui.misoto22.com/components/nav-item/llms.txt): A row in a sidebar. - [Card](https://ui.misoto22.com/components/card/llms.txt): A bounded surface, with no shadow under it. - [Table](https://ui.misoto22.com/components/table/llms.txt): A ruled data table — alignment, sorting and rules all per column. - [AppShell](https://ui.misoto22.com/components/app-shell/llms.txt): Two columns on a desktop, a drawer on a phone. - [Combobox](https://ui.misoto22.com/components/combobox/llms.txt): A select you can type into. - [DatePicker](https://ui.misoto22.com/components/date-picker/llms.txt): A date — or a span of them — chosen from a calendar. - [Slider](https://ui.misoto22.com/components/slider/llms.txt): A value chosen along a range. - [ToggleGroup](https://ui.misoto22.com/components/toggle-group/llms.txt): A segmented control: several options, one strip. - [Popover](https://ui.misoto22.com/components/popover/llms.txt): A panel anchored to a control, holding content you can interact with. - [Sheet](https://ui.misoto22.com/components/sheet/llms.txt): A panel docked to an edge of the viewport. - [ContextMenu](https://ui.misoto22.com/components/context-menu/llms.txt): The menu a right-click opens. - [SearchableMenu](https://ui.misoto22.com/components/searchable-menu/llms.txt): A menu of actions you can type into. - [Command](https://ui.misoto22.com/components/command/llms.txt): A filterable list of actions — the ⌘K surface. - [Collapsible](https://ui.misoto22.com/components/collapsible/llms.txt): One thing that opens, on its own. - [Calendar](https://ui.misoto22.com/components/calendar/llms.txt): A month, as a grid of days. - [ScrollArea](https://ui.misoto22.com/components/scroll-area/llms.txt): A box that scrolls, with a scrollbar that looks the same everywhere. ## Foundations - [Colour](https://ui.misoto22.com/foundations/colour/): A white ground, a near-black mark, three rule weights, and status. - [Typography](https://ui.misoto22.com/foundations/typography/): Three faces, one heading ladder, and nothing above the page title. - [Space & shape](https://ui.misoto22.com/foundations/space/): The page gutter, the measures, and the four radii. - [Motion](https://ui.misoto22.com/foundations/motion/): One curve, three durations, and a reduced-motion rule that is not optional. ## Templates - [Dashboard](https://ui.misoto22.com/templates/dashboard/): A console: a sidebar, a figure band, tabs, a filterable table and two job cards. - [Landing page](https://ui.misoto22.com/templates/landing/): A marketing page: a hero, a figure band, three pillars, a reversed pricing plate and an FAQ. ## Optional - [Everything, inline](https://ui.misoto22.com/llms-full.txt) — every component in one file. - [Themes](https://ui.misoto22.com/themes/) — the five presets, rendered. - [Changelog](https://ui.misoto22.com/changelog/) --- # Button The system’s action, as a pill that does not move on hover. - Group: Actions - Import: `import { Button } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/button/ - Related: floating-icon-button, spinner ## When to reach for it Anything that DOES something. If it navigates and looks like text, it is a link, not a ghost button. ## Accessibility - A native ``` ## Example — sizes ```tsx import { Button } from '@misoto22/design'
``` ## Example — states ```tsx import { Button } from '@misoto22/design'
``` ## Example — icon only ```tsx import { Button } from '@misoto22/design'
``` # FloatingIconButton A round action pinned to a screen corner. - Group: Actions - Import: `import { FloatingIconButton } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/floating-icon-button/ - Related: button ## When to reach for it A page-level affordance that must stay reachable while the reader scrolls — back to top, a mobile table of contents. ## Accessibility - label is the only name the control has; it is required rather than optional. - 44px square, which is the pointer-target floor (WCAG 2.5.8). ## FloatingIconButton A round action pinned to a screen corner. A translucent paper ground with a backdrop blur rather than a drop shadow — the White Reset has no elevation ramp, so what separates the control from the page under it is the blur and the hairline, not a glow. ### Props - `position` (required) — `'start' | 'end'`. Which bottom corner the control is pinned to, in READING order — `end` is the right in English and the left in Arabic. Naming the sides `left`/`right` would have hard-coded one script's layout into the API. - `label` (required) — `string`. Accessible name — the button has no visible text, so this is its only name. - `children` (required) — `ReactNode`. Also accepts: `Omit, 'children'>`. ## Example — default ```tsx import { FloatingIconButton } from '@misoto22/design'
``` # Badge A count or a state, set in mono so it reads as metadata. - Group: Display - Import: `import { Badge } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/badge/ - Related: tag, status-pill ## When to reach for it One fact about one record. If it names what something is ABOUT, that is a Tag. ## Accessibility - Not interactive. A badge with an onClick is a control a keyboard cannot reach. - The status tones double their colour with words, so the meaning survives monochrome and colour-blindness. ## Badge A count, a state, a small inline marker. Mono and small, so it reads as metadata rather than as content. A badge is not interactive — if it can be clicked or dismissed it is a `Tag` or a button, and giving this one an `onClick` produces a control a keyboard cannot reach. ### Props - `children` (required) — `ReactNode`. - `tone` — `BadgeTone` default `'neutral'`. `neutral` is the default and the right answer nearly always. The three status tones are the system's only chroma and are bound to STATE — a badge that is red because the design wanted a red badge is the thing this scale exists to prevent. Also accepts: `HTMLAttributes`. ## Example — tones ```tsx import { Badge } from '@misoto22/design'
12 Draft Deployed Degraded Failed
``` # Tag A subject label — a topic, a technology, a filter facet. - Group: Display - Import: `import { Tag } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/tag/ - Related: badge ## When to reach for it Several sit together and the reader scans them. One fact about one record is a Badge. ## Accessibility - Presentational. To filter with it, wrap it in a button and pass active, so the focus ring and the pressed state stay with the element that owns them. ## Tag A subject label — a topic, a technology, a filter facet. 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. Presentational on purpose. To make a tag filter a list, wrap it in a button or a link at the call site and pass `active` — that keeps the focus ring, the pressed state and the keyboard handling with the element that actually owns them. ### Props - `children` (required) — `ReactNode`. - `active` — `boolean` default `false`. Renders the pressed/selected look. Pair with `aria-pressed` on a wrapper button. Also accepts: `HTMLAttributes`. ## Example — default ```tsx import { Tag } from '@misoto22/design'
All TypeScript Rust Photography
``` # Kbd A key on a keyboard, set as one. - Group: Display - Import: `import { Kbd } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/kbd/ - Related: badge ## Accessibility - Renders , which carries the meaning a styled does not. ## Kbd A key on a keyboard, set as one. Renders a ``, which is the element that means this — a `` styled to look like a key tells a screen reader nothing. Sized in `em` so it tracks whatever type it sits beside rather than fixing itself at one px value, which is how the same shortcut ended up three different sizes across a page. ### Props - `children` (required) — `ReactNode`. Also accepts: `HTMLAttributes`. ## Example — default ```tsx import { Kbd } from '@misoto22/design'

Press K to open the palette, or Esc to close it.

``` # Avatar A person, as a circle, with initials until the image lands. - Group: Display - Import: `import { Avatar } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/avatar/ ## Accessibility - alt describes the person, not the picture. An empty string is correct when the name is already printed beside it. - The initials are aria-hidden — read aloud they are noise. ## Avatar A person, as a circle. Wraps Radix so the fallback appears only after the image has actually failed or is still loading — a hand-rolled `onError` swap flashes the initials on every render before the cache answers. ### Props - `src` — `string`. - `alt` (required) — `string`. Describes the person, not the picture. Empty string is correct and deliberate when the name is already printed beside the avatar — repeating it makes a screen reader say it twice. - `fallback` (required) — `string`. Shown while the image loads and if it never does. Usually initials. - `size` — `'sm' | 'md' | 'lg'` default `'md'`. Also accepts: `Omit, 'children'>`. ## Example — sizes ```tsx import { Avatar } from '@misoto22/design'
``` # StatusDot The dot beside a status word. - Group: Display - Import: `import { StatusDot } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/status-dot/ - Related: status-pill ## Accessibility - aria-hidden without exception: it repeats a state the adjacent label already names. - The halo is motion-safe, so a reader who asked for less motion gets a still dot. ## StatusDot The dot beside a status word. `aria-hidden` without exception: the dot repeats a state the adjacent label already names, and a screen reader announcing "available" twice is worse than not announcing the decoration at all. If a call site has no visible label, the fix is a label, not an `aria-label` on the dot. The halo is a separate absolutely-positioned ring rather than a box-shadow, because Law 2 of this system is that a shadow is never blurred — and it is motion-safe, so a reader who asked for less motion gets a static dot. ### Props - `size` — `'sm' | 'md'` default `'md'`. 7px / 8px. `md` is the default. - `tone` — `StatusTone` default `'success'`. - `pulse` — `boolean` default `true`. A halo that grows and dissolves, for "live right now". Off for a settled state. Also accepts: `HTMLAttributes`. ## Example — tones ```tsx import { StatusDot } from '@misoto22/design'
Live Degraded Down Idle
``` # StatusPill A live state, named: a dot plus an uppercase mono label. - Group: Display - Import: `import { StatusPill } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/status-pill/ - Related: status-dot, badge ## StatusPill 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. ### Props - `children` (required) — `ReactNode`. - `tone` — `StatusTone` default `'success'`. - `pulse` — `boolean` default `true`. Pulsing live dot (default) vs a settled one. Also accepts: `HTMLAttributes`. ## Example — default ```tsx import { StatusPill } from '@misoto22/design'
Available for work Partial outage
``` # LinkArrow The mark on a link that leaves the page. - Group: Display - Import: `import { LinkArrow } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/link-arrow/ ## Accessibility - aria-hidden, so it is not read as “north east arrow” in the middle of a sentence. - Sized in em, so it tracks whatever type it sits beside instead of competing with it. ## EXTERNAL_LINK_ARROW Re-export of `'↗'`. The glyph itself, exported so a non-React surface can use the same mark. ## LinkArrow The mark on a link that leaves the page — outbound, or on to a full index. Call sites used to write a bare `↗` inside the link text, which inherited the link's own size (a 17px arrow beside a 17px word, competing with it) and was read aloud as "north east arrow" because nothing hid it. `inline-block` is load-bearing twice over: it stops an underlined parent drawing its rule through the glyph, and it is what `align` is measured against. Sized in `em`, so it tracks whatever type it sits beside; coloured against paper, so a call site on a reversed plate passes its own colour. Also accepts: `HTMLAttributes`. ## Example — default ```tsx import { LinkArrow } from '@misoto22/design'

Read the whole thing

``` # Separator A rule, in the three weights a monochrome page needs. - Group: Display - Import: `import { Separator } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/separator/ ## When to reach for it Hairline between rows, edge between blocks, hard under a masthead. ## Accessibility - role="none" by default. A rule that only groups things visually must not be announced. ## Separator A rule. In a monochrome system the rule does the work colour would otherwise do, so it has three weights rather than one: hairline between rows, edge between blocks, hard under a masthead. Picking by name keeps a page from drifting into five hand-tuned greys. ### Props - `orientation` — `'horizontal' | 'vertical'` default `'horizontal'`. - `weight` — `'hairline' | 'edge' | 'hard'` default `'hairline'`. `hairline` divides rows inside one block; `edge` divides one block from the next. - `decorative` — `boolean` default `true`. A rule that only groups things visually is decoration and must not be announced. Set `false` when the rule genuinely separates two sections a screen reader should hear as distinct. Also accepts: `HTMLAttributes`. ## Example — weights ```tsx import { Separator } from '@misoto22/design'

hairline — between rows

edge — between blocks

hard — under a masthead

``` # FigureBand A row of counted facts, divided by hairlines and nothing else. - Group: Display - Import: `import { FigureBand } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/figure-band/ - Related: table ## Accessibility - A
: each cell is a term and its value, which a grid of divs cannot express. ## FigureBand A row of counted facts, divided by hairlines and nothing else. A `
`, because that is what this is: each cell is a term and its value, and a grid of `
`s tells a screen reader nothing about which number goes with which label. Each divider width names the cells that do NOT open a row rather than adding a rule and taking it back — an `undo` at equal specificity resolves on Tailwind's own sort order, which is not something a layout should depend on. Drawn this way, no edge is ever painted past the last column. ### Props - `figures` (required) — `Figure[]`. - `scale` — `'lead' | 'sub'` default `'lead'`. How large the value is set. `lead` is for a band that is the point of its page — a stats headline, where the numbers ARE the content. `sub` is for a band that supports the page around it, and keeps the ladder honest: a supporting figure must not be set at the same size as the page's own subject. - `label` — `string`. Names the band for assistive tech when it has no visible heading of its own. Also accepts: `Omit, 'children'>`. ## Example — default ```tsx import { FigureBand } from '@misoto22/design' ``` # Spinner The one “working” indicator — a ring, never a shimmer. - Group: Feedback - Import: `import { Spinner } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/spinner/ - Related: skeleton, progress ## When to reach for it A wait short enough that the shape of what is coming does not matter. Longer than that, use a Skeleton. ## Accessibility - label names the specific thing being waited on; three spinners all saying “Loading” tell a screen reader nothing. - label={null} silences it for use inside a control that already announces the operation. - Spins under motion-safe only; the static ring still reads as unfinished because the leading quarter is darker. ## Spinner 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. ### Props - `size` — `'sm' | 'md' | 'lg'` default `'md'`. 14px / 18px / 26px. Match the size of the type it sits beside. - `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. - `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. Also accepts: `HTMLAttributes`. ## Example — sizes ```tsx import { Spinner } from '@misoto22/design'
``` ## Example — on a filled ground ```tsx import { Button, Spinner } from '@misoto22/design'
Indexing 1,204 frames
``` # Skeleton The shape of the page, before the page. - Group: Feedback - Import: `import { Skeleton } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/skeleton/ - Related: spinner, empty-state ## When to reach for it A wait the reader would otherwise think was a broken page. A shape that describes what is coming beats a dot that describes nothing. ## Accessibility - One live region on the wrapper; every shape inside it is aria-hidden. - One pulse on the wrapper, not one per bar, so the page breathes together. ## Skeleton The fill every skeleton part is drawn from: `--stone`, and nothing else. Deliberately NOT a shimmer. A shimmer sweeps a highlight across the shape, which implies a light source; the White Reset has none, so the sweep reads as a bug rather than as loading. The whole page breathes together instead — see `SkeletonPage`, which owns the single pulse. Also accepts: `HTMLAttributes`. ## SkeletonLine A line of type. Height is the line's; width is how far it runs. Also accepts: `HTMLAttributes`. ## SkeletonBlock A block: an image mount, a plate, a chart. Also accepts: `HTMLAttributes`. ## SkeletonCircle A circle: an avatar, a marker. Also accepts: `HTMLAttributes`. ## SkeletonText A paragraph. The last line is short, because real prose ends mid-measure. A block of equal full-width bars reads as a table, and the reader then flinches when a paragraph arrives instead. ### Props - `lines` — `number` default `3`. How many lines of prose to stand in for. Also accepts: `HTMLAttributes`. ## SkeletonPage The frame a skeleton sits in: the live region, and the single pulse. One `animate-pulse` on the wrapper rather than one per part, so the page breathes together instead of shimmering out of phase — twenty independently animated bars is a visual noise floor, not a loading state. A skeleton whose shape does not match what replaces it is worse than none: it promises a layout and then the page jumps out from under the reader. Compose the parts to match the real thing, and change both together. ### Props - `label` (required) — `string`. What is loading, in words. Announced once; the shapes themselves are `aria-hidden`, so this sentence is the entire experience for a screen reader and "Loading" alone tells it nothing. - `children` (required) — `ReactNode`. Also accepts: `HTMLAttributes`. ## Example — record list ```tsx import { SkeletonBlock, SkeletonLine, SkeletonPage, SkeletonText } from '@misoto22/design'
{[0, 1].map((row) => (
))}
``` ## Example — parts ```tsx import { SkeletonBlock, SkeletonCircle, SkeletonLine, SkeletonPage, SkeletonText } from '@misoto22/design'
``` # Progress A bar that fills, or sweeps when the end is unknown. - Group: Feedback - Import: `import { Progress } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/progress/ - Related: spinner ## Accessibility - Omitting value drops aria-valuenow, so a screen reader hears “indeterminate” rather than a number that is a guess. - label is required — a bare bar announces nothing. ## Progress A bar that fills, or sweeps when the end is unknown. Flat: a track in `--stone`, a fill in `--ink`. The White Reset has no gradient and no glow, so the only thing carrying the reading is the boundary between the two. ### Props - `value` — `number | null` default `null`. 0–100. Omit (or pass `null`) when the duration is genuinely unknown — the bar then sweeps instead of filling, and Radix drops `aria-valuenow` so a screen reader is told "indeterminate" rather than a number that is a guess. - `label` (required) — `string`. Names what is progressing. Required: a bare bar announces nothing. - `showValue` — `boolean` default `false`. Prints the percentage above the bar. Only meaningful when `value` is set. Also accepts: `Omit, 'value'>`. ## Example — determinate ```tsx import { Progress } from '@misoto22/design'
``` # Alert A message about the page, in place. - Group: Feedback - Import: `import { Alert } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/alert/ - Related: toast, error-state ## When to reach for it Something the reader needs to see and may need to act on. Something they only need to notice is a Toast. ## Accessibility - danger is role="alert" and interrupts; the other three are role="status" and wait for a pause. - Colour is doubled by an icon and by the words. ## Alert A message about the page, in place. Carries `role="alert"` for the danger tone and `role="status"` for the rest, which is the difference between interrupting the reader and waiting for a pause. Getting that backwards is the usual accessibility failure here: a "saved" toast that talks over someone mid-sentence, or a payment error that is never announced at all. The tone is the message's SEVERITY, not its decoration. Colour is doubled by an icon and by the words, so the meaning survives both monochrome printing and colour-blindness. ### Props - `tone` — `AlertTone` default `'info'`. - `title` — `ReactNode`. - `children` — `ReactNode`. - `action` — `ReactNode`. Optional action — a retry, a link to the settings that fix this. - `hideIcon` — `boolean` default `false`. Hides the leading icon for a dense inline notice. Also accepts: `Omit, 'title'>`. ## Example — tones ```tsx import { Alert, Button } from '@misoto22/design'
A migration is running; edits are paused until it finishes. misoto22-site is live at commit a1b2c3d. Rotate it before the next release. Try again} > The file exceeds the 25 MB limit.
``` # EmptyState A collection with nothing in it — yet. - Group: Feedback - Import: `import { EmptyState } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/empty-state/ - Related: error-state, skeleton ## When to reach for it Nothing went wrong. The copy says what to do, not what failed. ## EmptyState A collection with nothing in it — yet. Distinct from `ErrorState`: nothing went wrong here, so the copy says what to do rather than what failed. The two are different components because the shared temptation — one component with a `variant` — produces error pages that read like empty folders. ### Props - `icon` — `LucideIcon`. Optional lucide icon, shown muted inside a quiet circle. - `title` (required) — `ReactNode`. - `description` — `ReactNode`. - `action` — `ReactNode`. The one thing to do next. An empty state without an action is a dead end. Also accepts: `Omit, 'title'>`. ## Example — default ```tsx import { Button, EmptyState } from '@misoto22/design' New project} /> ``` # ErrorState A page that could not be shown. - Group: Feedback - Import: `import { ErrorState } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/error-state/ - Related: empty-state, alert ## Accessibility - The big status code is aria-hidden; the heading immediately after says the same thing in words. ## ERROR_ACTION_CLASS Re-export of `'text-sm text-(--ink) underline decoration-(--rule-2) underline-offset-4 transition-colors duration-(--duration-fast) hover:decoration-(--ink)'`. The recovery affordance's look, exported so every error page offers the same one rather than each inventing a link style. ## ErrorState A page that could not be shown. The code is set at the top of the type ladder and marked `aria-hidden` — read aloud, "404" before the sentence explaining it is noise, and the heading immediately after says the same thing in words. ### Props - `code` (required) — `ReactNode`. The status, set large — "404", "500". Decorative; the heading carries the meaning. - `heading` (required) — `ReactNode`. - `message` (required) — `ReactNode`. - `action` (required) — `ReactNode`. The way back. Render it with . Also accepts: `HTMLAttributes`. ## Example — not found ```tsx import { ERROR_ACTION_CLASS, ErrorState } from '@misoto22/design' Back home} /> ``` # Toast A transient confirmation, mounted once near the app root. - Group: Feedback - Import: `import { Toast } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/toast/ - Related: alert ## When to reach for it Something succeeded and needs no response. A toast is dismissed by time, and time is not an acknowledgement. ## Toaster 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. ## Example — default ```tsx import { Button, Toaster, toast } from '@misoto22/design'
``` # Field A labelled form row: label, control, and the one message below it. - Group: Forms - Import: `import { Field } from '@misoto22/design'` - Page: https://ui.misoto22.com/components/field/ - Related: input, select ## Accessibility - Generates an id when none is given, so the label always points at something. - Wires aria-describedby, aria-required and aria-invalid onto the control, so validation is announced and not merely drawn. - hint and error are one slot: when a field is wrong, the thing to read is what is wrong with it. ## Field A labelled form row: label, control, and the one message below it. The visible message is only half of accessible validation — it must also reach the control. This wires `aria-describedby`, `aria-required` and `aria-invalid` onto the single control child so the requirement and the error are announced, not merely drawn (WCAG 1.3.1 / 3.3.1 / 4.1.2). An earlier version derived the message id from `htmlFor`, which meant a caller who left `htmlFor` off got a hint that was rendered and never announced — the failure was invisible in the browser and total for a screen reader. The id is now generated when it is not supplied. `hint` and `error` are one slot, not two stacked messages: when a field is wrong, the thing to read is what is wrong with it. ### Props - `label` — `ReactNode`. Visible label text; renders a `--danger` asterisk when `required`. - `htmlFor` — `string`. The control's `id`. Optional: when omitted, the field generates one and puts it on the control child itself, so the label still points at something. Pass it explicitly when the id has to be stable across renders — a form library referencing it by name, say. - `hint` — `ReactNode`. Helper copy shown below the control when there is no `error`. - `error` — `ReactNode`. Validation message; takes precedence over `hint` when present. - `required` — `boolean`. - `children` (required) — `ReactNode`. Also accepts: `HTMLAttributes`. ## Example — states ```tsx import { Field, Input, Select, SelectItem, Textarea } from '@misoto22/design'