ionbase-ui 0.18.0 → 0.19.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.
@@ -4,6 +4,73 @@
4
4
  "propsType": "LinkProps",
5
5
  "description": "Link — Figma `Link` (774:1516).\n\nRenders an `<a>` when given an `href` and a `<button>` otherwise, the same\njudgment `NavItem` makes: the element follows what the caller is actually\nbuilding rather than a separate `as` prop. A link that does not navigate is\na button, and shipping one as an anchor breaks middle-click, \"open in new\ntab\" and the screen-reader announcement all at once.\n\nNO SIZE PROP, DELIBERATELY. A link is an inline element and inherits its\ntype from the text around it — `font-size: inherit` in the stylesheet, and\nicons sized in `em` so they scale with it. Figma has to draw its variants at\nsome concrete size (16/24, `type/body`), but that is one sample of an\ninheriting element, not a specification. Do not add a size ladder to match\nthe drawing.",
6
6
  "import": "import { Link } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "Navigation to somewhere else. Renders an <a> when given `href`, a <button> otherwise.",
9
+ "useWhen": [
10
+ "the interaction takes the user to a different page, view or resource",
11
+ "the destination should support middle-click, open-in-new-tab and copy-link"
12
+ ],
13
+ "useInstead": [
14
+ {
15
+ "when": "the interaction changes something rather than going somewhere",
16
+ "use": "Button",
17
+ "why": "a link that does not navigate breaks middle-click, open-in-new-tab and the screen-reader announcement all at once"
18
+ },
19
+ {
20
+ "when": "it is a primary navigation destination in a header or sidebar",
21
+ "use": "NavItem"
22
+ }
23
+ ],
24
+ "variants": {
25
+ "variant": {
26
+ "inline": {
27
+ "use": "the default — a link inside a paragraph. Underlined in every state, because a link in body copy must not rely on colour alone (WCAG 1.4.1)"
28
+ },
29
+ "standalone": {
30
+ "use": "a link that already reads as interactive from its position — a card footer, an action row. Underlines on hover only"
31
+ }
32
+ }
33
+ },
34
+ "slots": {
35
+ "startIcon": {
36
+ "accepts": "Icon",
37
+ "note": "omit Icon's `label` — the link text already names it"
38
+ },
39
+ "endIcon": {
40
+ "accepts": "Icon",
41
+ "note": "the external-link or arrow affordance; same rule about `label`"
42
+ }
43
+ },
44
+ "a11y": {
45
+ "role": "link when `href` is set, button when it is not",
46
+ "guarantees": [
47
+ "the rendered element follows what the caller is actually building, so the announcement is always honest",
48
+ "`inline` stays underlined in every state, so it never depends on colour alone"
49
+ ],
50
+ "requires": [
51
+ "link text that makes sense read on its own — screen-reader users navigate by a list of links"
52
+ ]
53
+ },
54
+ "antiPatterns": [
55
+ {
56
+ "dont": "\"click here\" or \"read more\" as the whole link text",
57
+ "why": "out of context in a links list it names nothing"
58
+ },
59
+ {
60
+ "dont": "adding a size prop or wrapper to scale it",
61
+ "why": "a link is inline and inherits its type from the text around it, by design — icons are sized in `em` to follow"
62
+ },
63
+ {
64
+ "dont": "`target=\"_blank\"` with no warning in the text or endIcon",
65
+ "why": "an unannounced new tab disorients screen-reader and low-vision users"
66
+ }
67
+ ],
68
+ "deprecated": [
69
+ {
70
+ "prop": "disabled",
71
+ "replacement": "isDisabled"
72
+ }
73
+ ],
7
74
  "stylesheet": "src/styles/link.css",
8
75
  "tokens": [
9
76
  "--border-width-thick",
@@ -4,6 +4,55 @@
4
4
  "propsType": "LogoProps",
5
5
  "description": "The full lockup: mark + wordmark, matching Figma's `Logo-Ionbase`\n(52:21369).\n\nFigma also has a `Property=Name` axis — wordmark only, no mark — but both\nof its variants currently render the placeholder text \"raza\" rather than\n\"IonBase\", in an unbound raw colour. That is leftover debug content, not a\nreal asset, so it is not implemented here. Reproducing it would ship a\nstranger's name into a design system; extending `LogoMark` alone already\ncovers \"no wordmark\" for the cases that are real.",
6
6
  "import": "import { Logo } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "The full IonBase lockup — mark plus wordmark.",
9
+ "useWhen": [
10
+ "the product needs to identify itself: header, sign-in page, empty state, marketing surface"
11
+ ],
12
+ "useInstead": [
13
+ {
14
+ "when": "space is tight, or a wordmark already appears nearby",
15
+ "use": "LogoMark"
16
+ },
17
+ {
18
+ "when": "you need any graphic that is not the brand",
19
+ "use": "Icon"
20
+ }
21
+ ],
22
+ "variants": {
23
+ "size": {
24
+ "sm": {
25
+ "use": "the default — a 24px mark, which is what a header bar wants"
26
+ },
27
+ "lg": {
28
+ "use": "a 32px mark — sign-in, splash, or anywhere the brand is the subject"
29
+ }
30
+ },
31
+ "wordmark": {
32
+ "vector": {
33
+ "use": "the default and the safe choice — Figma's serif logotype as frozen artwork, immune to font substitution and visually distinct from the UI type"
34
+ },
35
+ "text": {
36
+ "use": "live, selectable Host Grotesk at `text/tertiary` — for surfaces where the name should behave like text (copyable, searchable) rather than like a mark"
37
+ }
38
+ }
39
+ },
40
+ "a11y": {
41
+ "guarantees": [
42
+ "the wordmark carries the accessible name, so the mark beside it is not announced twice",
43
+ "artwork uses `currentColor` rather than Figma's baked hex, so it themes with the rest of the icon layer"
44
+ ]
45
+ },
46
+ "antiPatterns": [
47
+ {
48
+ "dont": "recolouring the lockup with your own fill",
49
+ "why": "it inherits `icon/default`; overriding it is how a brand mark ends up failing contrast in one theme"
50
+ },
51
+ {
52
+ "dont": "putting the Logo inside a Link with its own text \"IonBase\"",
53
+ "why": "the name is announced twice; label the link, not both"
54
+ }
55
+ ],
7
56
  "stylesheet": "src/styles/logo.css",
8
57
  "tokens": [
9
58
  "--font-family-sans",
@@ -4,6 +4,44 @@
4
4
  "propsType": "LogoMarkProps",
5
5
  "description": "The bare icon glyph, no wordmark — what `Header` actually uses.\n\nColour comes from the `.ion-logo__mark` class (`icon/default`), not the SVG\nitself, so it themes the same way every other icon in the system does.",
6
6
  "import": "import { LogoMark } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "The bare mark, no wordmark — what Header uses.",
9
+ "useWhen": [
10
+ "a wordmark is already visible nearby, or the bar is too narrow for the lockup",
11
+ "a favicon-scale identity marker inside the product"
12
+ ],
13
+ "useInstead": [
14
+ {
15
+ "when": "this is the only place the product names itself",
16
+ "use": "Logo",
17
+ "why": "a mark alone identifies nothing to someone who has not learned it yet"
18
+ }
19
+ ],
20
+ "variants": {
21
+ "size": {
22
+ "sm": {
23
+ "use": "the default, 24px — header bars"
24
+ },
25
+ "lg": {
26
+ "use": "32px — sign-in and other brand-forward surfaces"
27
+ }
28
+ }
29
+ },
30
+ "a11y": {
31
+ "requires": [
32
+ "`label` when the mark stands alone — it is the only thing naming it"
33
+ ],
34
+ "notes": [
35
+ "Omit `label` when a visible wordmark sits beside it; that text already carries the name."
36
+ ]
37
+ },
38
+ "antiPatterns": [
39
+ {
40
+ "dont": "`label=\"logo\"`",
41
+ "do": "`label=\"IonBase\"`",
42
+ "why": "the name of the brand is the useful announcement; the word \"logo\" is not"
43
+ }
44
+ ],
7
45
  "stylesheet": "src/styles/logo.css",
8
46
  "tokens": [
9
47
  "--font-family-sans",
@@ -4,6 +4,60 @@
4
4
  "propsType": "MenuProps",
5
5
  "description": "Menu is the list surface, not a popover.\n\nFigma models the list alone — there is no trigger, anchor or open state in\nthe design — so this renders the surface and nothing else. Positioning it\nagainst a button is the caller's job until Figma has a component that says\nhow. Building a popover here would be inventing design, and it would be the\nhard half to unpick later.\n\n`role=\"menu\"` is deliberately NOT set. A real ARIA menu owes the user\nroving-tabindex arrow navigation, typeahead and focus containment; claiming\nthe role without them is worse for a screen-reader user than an honest list,\nbecause it promises interactions that are not there. When the popover exists,\nthe role comes with it.",
6
6
  "import": "import { Menu } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "The list surface only — no trigger, no anchor, no open state. Positioning it is the caller's job.",
9
+ "useWhen": [
10
+ "rendering a list of actions or options as a surface you have already positioned",
11
+ "the rows need icons, a selected check, or two lines — more than a native <select> can hold"
12
+ ],
13
+ "useInstead": [
14
+ {
15
+ "when": "you want a self-contained dropdown with a trigger and open state",
16
+ "use": "Popover",
17
+ "why": "Menu is the surface alone; Popover owns the trigger, positioning, focus containment and Escape"
18
+ },
19
+ {
20
+ "when": "one value is picked from a modest list of plain text options",
21
+ "use": "Select",
22
+ "why": "the native picker brings keyboard handling, type-ahead and the mobile platform picker for free"
23
+ },
24
+ {
25
+ "when": "it is primary navigation rather than a dropdown",
26
+ "use": "NavItem"
27
+ }
28
+ ],
29
+ "composition": {
30
+ "order": [
31
+ "Menu",
32
+ "MenuItem"
33
+ ],
34
+ "example": "<Menu><MenuItem icon={<Icon as={Copy} />}>Duplicate</MenuItem><MenuItem isSelected>Archive</MenuItem></Menu>"
35
+ },
36
+ "slots": {
37
+ "children": {
38
+ "accepts": "MenuItem"
39
+ }
40
+ },
41
+ "a11y": {
42
+ "role": "none — it is a plain <ul>",
43
+ "guarantees": [
44
+ "no `role=\"menu\"`, deliberately: a real ARIA menu owes the user roving tabindex, arrow-key navigation and typeahead, and claiming the role without them promises interactions that are not there"
45
+ ],
46
+ "notes": [
47
+ "Each MenuItem is a real <button>, so Tab reaches every row. That is honest, and it is not the same as arrow-key menu navigation.",
48
+ "The role arrives when the popover that owns it does."
49
+ ]
50
+ },
51
+ "antiPatterns": [
52
+ {
53
+ "dont": "adding `role=\"menu\"` yourself",
54
+ "why": "the keyboard behaviour that role promises is not implemented; an honest list is better for a screen-reader user than a lying menu"
55
+ },
56
+ {
57
+ "dont": "using Menu as a dropdown without positioning it",
58
+ "why": "it renders in flow — it has no anchor logic of its own"
59
+ }
60
+ ],
7
61
  "stylesheet": "src/styles/menu.css",
8
62
  "tokens": [
9
63
  "--border-focus",
@@ -3,6 +3,57 @@
3
3
  "source": "src/components/Menu.tsx",
4
4
  "propsType": "MenuItemProps",
5
5
  "import": "import { MenuItem } from 'ionbase-ui';",
6
+ "status": "stable",
7
+ "summary": "One row in a Menu. A real <button> inside an <li>, with an optional leading icon and a trailing selected check.",
8
+ "useWhen": [
9
+ "a row in a Menu surface"
10
+ ],
11
+ "useInstead": [
12
+ {
13
+ "when": "it is a top-level navigation destination",
14
+ "use": "NavItem"
15
+ },
16
+ {
17
+ "when": "it is a standalone action outside a list",
18
+ "use": "Button"
19
+ }
20
+ ],
21
+ "slots": {
22
+ "icon": {
23
+ "accepts": "Icon",
24
+ "note": "leading icon; omit its `label` — the row's text names it"
25
+ },
26
+ "children": {
27
+ "accepts": "text",
28
+ "note": "the row label"
29
+ }
30
+ },
31
+ "a11y": {
32
+ "role": "button",
33
+ "guarantees": [
34
+ "`isSelected` sets `aria-pressed`, so the selected state is announced and not only drawn",
35
+ "the check glyph always occupies its slot, so rows never reflow as the selection moves"
36
+ ],
37
+ "requires": [
38
+ "a text label in `children`"
39
+ ]
40
+ },
41
+ "antiPatterns": [
42
+ {
43
+ "dont": "using MenuItem outside a Menu",
44
+ "why": "it renders an <li>, which is only valid inside a list"
45
+ },
46
+ {
47
+ "dont": "an icon-only MenuItem with no children",
48
+ "why": "the row announces nothing"
49
+ }
50
+ ],
51
+ "deprecated": [
52
+ {
53
+ "prop": "disabled",
54
+ "replacement": "isDisabled"
55
+ }
56
+ ],
6
57
  "stylesheet": "src/styles/menu.css",
7
58
  "tokens": [
8
59
  "--border-focus",
@@ -4,6 +4,59 @@
4
4
  "propsType": "NavItemProps",
5
5
  "description": "Nav Item renders an `<a>` when given `href`, a `<button>` otherwise —\nchosen by what the caller is actually building, the same judgment call\nMenu and Select make. A primary nav bar is links; a nav item with\n`showChevron` that opens a menu (no `href` of its own) is a button.\n\nInteraction state comes from React Aria rather than CSS pseudo-classes,\nmatching Button: `useHover` is pointer-aware so a tap does not stay\n\"hovered\" until the next tap elsewhere, and `useFocusRing` shows the ring\nonly for keyboard navigation. The CSS keeps its own `:hover` /\n`:focus-visible` rules so the stylesheet still works without React.",
6
6
  "import": "import { NavItem } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "A primary navigation destination. Renders an <a> when given `href`, a <button> otherwise.",
9
+ "useWhen": [
10
+ "building a header nav bar, a sidebar or a section switcher",
11
+ "an item opens a menu rather than navigating — pass `showChevron` and no `href`"
12
+ ],
13
+ "useInstead": [
14
+ {
15
+ "when": "the link sits inside body copy or a content block",
16
+ "use": "Link"
17
+ },
18
+ {
19
+ "when": "it is a row in a dropdown list rather than a top-level destination",
20
+ "use": "MenuItem"
21
+ }
22
+ ],
23
+ "slots": {
24
+ "icon": {
25
+ "accepts": "Icon",
26
+ "note": "leading icon; omit its `label` — the item's text names it"
27
+ }
28
+ },
29
+ "a11y": {
30
+ "role": "link when `href` is set, button when it is not",
31
+ "guarantees": [
32
+ "hover state comes from React Aria's pointer-aware `useHover`, so a tap does not stay hovered until the next tap elsewhere",
33
+ "the focus ring shows for keyboard navigation only",
34
+ "the CSS keeps its own `:hover` / `:focus-visible` rules, so the styling survives without React"
35
+ ],
36
+ "requires": [
37
+ "an accessible name — `children`, or `aria-label` on an icon-only item"
38
+ ],
39
+ "notes": [
40
+ "NavItem is one item, not the landmark. Wrap the set in a <nav> with its own accessible name."
41
+ ]
42
+ },
43
+ "antiPatterns": [
44
+ {
45
+ "dont": "`showChevron` on a plain link that navigates",
46
+ "why": "the chevron promises a menu; nothing opens"
47
+ },
48
+ {
49
+ "dont": "marking the current page with colour only",
50
+ "do": "`aria-current=\"page\"`",
51
+ "why": "a screen-reader user is otherwise never told where they are"
52
+ }
53
+ ],
54
+ "deprecated": [
55
+ {
56
+ "prop": "disabled",
57
+ "replacement": "isDisabled"
58
+ }
59
+ ],
7
60
  "stylesheet": "src/styles/nav-item.css",
8
61
  "tokens": [
9
62
  "--border-focus",
@@ -4,6 +4,66 @@
4
4
  "propsType": "PhoneInputProps",
5
5
  "description": "PhoneInput — Figma `Input/Phone` (80:372).\n\nA dial-code block butted against an Input, sharing one outline: the block\ncarries the left radii and the control the right, so the seam is square on\nboth sides and reads as a single control. Both keep their full 1px border,\nwhich is what Figma draws — the 2px seam is the two strokes meeting, not an\naccident.\n\nThree sizes, no State axis. Every interaction state is the Input's, reached\nthrough the ordinary props, because Figma composes this from the same `Input`\ninstance rather than redrawing it.\n\nWHAT THIS DELIBERATELY DOES NOT DO\n\nIt does not pick countries. The chevron implies a menu and Figma specifies no\nopen state for it — no list, no flags, no search, no selected state. Country\ndata and the picker are application concerns with real editorial weight (which\nterritories, which names, which order), and inventing them here would be\ndesigning rather than implementing.\n\nSo the trigger is a real `<button>` with an accessible name, and\n`countryButtonProps` is how you wire it to a `Menu`, a popover or your own\nlistbox — including the `aria-haspopup` and `aria-expanded` that only the\nthing owning the popup can set honestly.",
6
6
  "import": "import { PhoneInput } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "A dial-code block butted against an Input, sharing one outline. It does not pick countries.",
9
+ "useWhen": [
10
+ "collecting a phone number where the country calling code matters"
11
+ ],
12
+ "useInstead": [
13
+ {
14
+ "when": "the number is free-form and the code does not matter",
15
+ "use": "Input"
16
+ }
17
+ ],
18
+ "variants": {
19
+ "size": {
20
+ "sm": {
21
+ "use": "dense forms"
22
+ },
23
+ "md": {
24
+ "use": "the default"
25
+ },
26
+ "lg": {
27
+ "use": "prominent single-field forms"
28
+ }
29
+ }
30
+ },
31
+ "slots": {
32
+ "trailingIcon": {
33
+ "accepts": "Icon",
34
+ "note": "passed through to the Input"
35
+ },
36
+ "countryButtonProps": {
37
+ "accepts": "button props",
38
+ "note": "how a country picker is attached — spread your trigger's props here, including the `aria-haspopup` and `aria-expanded` only the thing owning the popup can set honestly"
39
+ }
40
+ },
41
+ "a11y": {
42
+ "guarantees": [
43
+ "the dial-code trigger is a real <button> with an accessible name",
44
+ "every interaction state is the Input's, reached through the ordinary Input props"
45
+ ],
46
+ "requires": [
47
+ "`label`, or `aria-label` when no visible label is rendered",
48
+ "`countryLabel` if you change the default — the trigger shows only `+1`, which names a value rather than an action"
49
+ ]
50
+ },
51
+ "antiPatterns": [
52
+ {
53
+ "dont": "expecting the chevron to open a country list",
54
+ "why": "the picker is deliberately not implemented — country data and ordering are editorial application decisions, and Figma specifies no open state. Wire your own through `countryButtonProps`"
55
+ },
56
+ {
57
+ "dont": "wrapping it in your own bordered container",
58
+ "why": "the block and the control already share one outline; a second border reads as two controls"
59
+ }
60
+ ],
61
+ "deprecated": [
62
+ {
63
+ "prop": "disabled",
64
+ "replacement": "isDisabled"
65
+ }
66
+ ],
7
67
  "stylesheet": "src/styles/phone-input.css",
8
68
  "tokens": [
9
69
  "--border-default",
@@ -4,6 +4,98 @@
4
4
  "propsType": "PopoverProps",
5
5
  "description": "Popover — Figma `Popover` (825:1853).\n\nSITS BETWEEN TOOLTIP AND MODAL. A tooltip is a hint that cannot hold\nfocusable content; a modal is a task that takes over the page. A popover\nholds interactive content but stays attached to the control that opened it.\n\n`usePopover` contains focus, closes on Escape or an outside click, and hides\nthe rest of the page from assistive tech while open — the same guarantees\nModal gives. What differs is the framing, and it is deliberate: no visible\nscrim, anchored to its trigger, and `surface/raised` rather than Modal's\n`surface/overlay`. It reads as attached to the page rather than replacing it.",
6
6
  "import": "import { Popover } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "An anchored panel that holds interactive content. Sits between Tooltip and Modal.",
9
+ "useWhen": [
10
+ "the content is interactive — a form, a filter, a list of actions — but should stay attached to the control that opened it",
11
+ "the surrounding page should stay visible and in context"
12
+ ],
13
+ "useInstead": [
14
+ {
15
+ "when": "the content is a short hint with nothing focusable in it",
16
+ "use": "Tooltip",
17
+ "why": "a tooltip cannot be focused or scrolled and closes when the pointer leaves"
18
+ },
19
+ {
20
+ "when": "the task takes over the page or must be completed before anything else",
21
+ "use": "Modal"
22
+ },
23
+ {
24
+ "when": "you only need the list surface and will position it yourself",
25
+ "use": "Menu"
26
+ }
27
+ ],
28
+ "variants": {
29
+ "placement": {
30
+ "top": {
31
+ "use": "above the trigger"
32
+ },
33
+ "bottom": {
34
+ "use": "below the trigger — the usual choice for a control in a header"
35
+ },
36
+ "left": {
37
+ "use": "to the left of the trigger"
38
+ },
39
+ "right": {
40
+ "use": "to the right of the trigger"
41
+ }
42
+ },
43
+ "size": {
44
+ "sm": {
45
+ "use": "a couple of actions or one short field"
46
+ },
47
+ "md": {
48
+ "use": "the default"
49
+ },
50
+ "lg": {
51
+ "use": "a small form or a filter panel"
52
+ }
53
+ }
54
+ },
55
+ "slots": {
56
+ "children": {
57
+ "accepts": "Button",
58
+ "note": "the trigger. Must forward both a ref and DOM props — every component in this library does"
59
+ },
60
+ "content": {
61
+ "accepts": "any"
62
+ },
63
+ "title": {
64
+ "accepts": "text",
65
+ "note": "also gives the dialog its accessible name"
66
+ },
67
+ "footer": {
68
+ "accepts": "Button",
69
+ "note": "action row, right-aligned"
70
+ }
71
+ },
72
+ "a11y": {
73
+ "role": "dialog",
74
+ "guarantees": [
75
+ "focus is contained while open, Escape and outside clicks close it, and the rest of the page is hidden from assistive tech",
76
+ "`placement` is a preference, not a guarantee — it flips when there is no room, and the arrow follows"
77
+ ],
78
+ "requires": [
79
+ "`title`, or `aria-label` on the popover — a dialog with no name announces nothing about what opened"
80
+ ],
81
+ "notes": [
82
+ "`placement` names where the POPOVER sits, not where the arrow points."
83
+ ]
84
+ },
85
+ "antiPatterns": [
86
+ {
87
+ "dont": "a function-component trigger that drops its ref",
88
+ "why": "it renders, but the popover has nothing to position against"
89
+ },
90
+ {
91
+ "dont": "using a Popover where a Modal belongs",
92
+ "why": "a popover closes on an outside click; a task that must be finished should not be dismissible by a stray click"
93
+ },
94
+ {
95
+ "dont": "nesting a Popover inside another Popover",
96
+ "why": "two overlapping focus containments, and dismissing the outer one takes the inner one with it"
97
+ }
98
+ ],
7
99
  "stylesheet": "src/styles/popover.css",
8
100
  "tokens": [
9
101
  "--border-subtle",
@@ -3,6 +3,83 @@
3
3
  "source": "src/components/Radio.tsx",
4
4
  "propsType": "RadioProps",
5
5
  "import": "import { Radio } from 'ionbase-ui';",
6
+ "status": "stable",
7
+ "summary": "One option inside a RadioGroup. Meaningless on its own — the group owns the name and the selected value.",
8
+ "useWhen": [
9
+ "a child of RadioGroup"
10
+ ],
11
+ "useInstead": [
12
+ {
13
+ "when": "the user may pick more than one",
14
+ "use": "Checkbox"
15
+ },
16
+ {
17
+ "when": "there are more than about five options",
18
+ "use": "Select",
19
+ "why": "a long radio list costs vertical space no one reads"
20
+ }
21
+ ],
22
+ "variants": {
23
+ "size": {
24
+ "sm": {
25
+ "use": "dense forms"
26
+ },
27
+ "md": {
28
+ "use": "the default"
29
+ },
30
+ "lg": {
31
+ "use": "touch targets and prominent choices"
32
+ }
33
+ },
34
+ "intent": {
35
+ "brand": {
36
+ "use": "the default"
37
+ },
38
+ "neutral": {
39
+ "use": "a choice with no positive reading — sort order, display mode"
40
+ },
41
+ "danger": {
42
+ "use": "selecting a destructive option"
43
+ }
44
+ }
45
+ },
46
+ "slots": {
47
+ "children": {
48
+ "accepts": "text",
49
+ "note": "the option's visible label"
50
+ }
51
+ },
52
+ "a11y": {
53
+ "role": "radio",
54
+ "guarantees": [
55
+ "`name` and the selected value come from the group's context, so no Radio has to repeat them",
56
+ "`isDisabled` falls back to the group's"
57
+ ],
58
+ "requires": [
59
+ "a `value` unique within the group",
60
+ "a text label in `children`"
61
+ ]
62
+ },
63
+ "antiPatterns": [
64
+ {
65
+ "dont": "a Radio outside a RadioGroup",
66
+ "why": "it has no name and no siblings, so it can be selected but never unselected"
67
+ },
68
+ {
69
+ "dont": "wiring `checked`/`onChange` on each Radio",
70
+ "why": "the group owns selection; per-option wiring is what the context exists to remove"
71
+ },
72
+ {
73
+ "dont": "a group of one radio",
74
+ "why": "a single radio cannot be cleared once chosen — that is a Checkbox"
75
+ }
76
+ ],
77
+ "deprecated": [
78
+ {
79
+ "prop": "disabled",
80
+ "replacement": "isDisabled"
81
+ }
82
+ ],
6
83
  "stylesheet": "src/styles/radio.css",
7
84
  "tokens": [
8
85
  "--border-disabled",