@adia-ai/web-components 0.0.15 → 0.0.17

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.
Files changed (43) hide show
  1. package/components/alert/alert.css +5 -0
  2. package/components/alert/alert.js +4 -2
  3. package/components/button/button.js +4 -1
  4. package/components/chat/chat-input.js +13 -2
  5. package/components/description-list/description-list.js +4 -3
  6. package/components/drawer/drawer.css +13 -6
  7. package/components/field/field.css +113 -63
  8. package/components/field/field.js +44 -142
  9. package/components/icon/icon.a2ui.json +1 -1
  10. package/components/icon/icon.css +16 -0
  11. package/components/icon/icon.js +18 -0
  12. package/components/icon/icon.yaml +6 -2
  13. package/components/index.js +1 -0
  14. package/components/input/input.a2ui.json +1 -1
  15. package/components/input/input.css +29 -24
  16. package/components/input/input.js +36 -9
  17. package/components/input/input.yaml +3 -1
  18. package/components/option-card/option-card.a2ui.json +262 -0
  19. package/components/option-card/option-card.css +219 -0
  20. package/components/option-card/option-card.js +158 -0
  21. package/components/option-card/option-card.yaml +234 -0
  22. package/components/rating/rating.a2ui.json +10 -0
  23. package/components/rating/rating.yaml +8 -0
  24. package/components/segment/segment.a2ui.json +5 -0
  25. package/components/segment/segment.css +14 -0
  26. package/components/segment/segment.js +21 -1
  27. package/components/segment/segment.yaml +5 -0
  28. package/components/select/select.css +6 -2
  29. package/components/textarea/textarea.css +1 -1
  30. package/components/textarea/textarea.js +2 -2
  31. package/core/data-stream.js +21 -0
  32. package/core/form.js +5 -0
  33. package/core/index.js +2 -0
  34. package/core/streams-bridge.js +96 -0
  35. package/package.json +1 -1
  36. package/patterns/app-nav-group/app-nav-group.css +2 -2
  37. package/patterns/app-shell/css/app-shell.tokens.css +5 -1
  38. package/patterns/section-nav/section-nav.css +4 -3
  39. package/styles/colors/semantics.css +11 -6
  40. package/styles/components.css +1 -0
  41. package/styles/prose.css +3 -7
  42. package/styles/tokens.css +7 -4
  43. package/styles/typography.css +3 -3
@@ -17,4 +17,20 @@
17
17
  width: 100%;
18
18
  height: 100%;
19
19
  }
20
+
21
+ /* ── Named size scale ─────────────────────────────────────────────
22
+ sm/md/lg are also driven by the universal `[size]` token system
23
+ on ancestors (tokens.css §SIZE PRESETS) — these rules let an
24
+ icon-ui set its own size locally. xs / xl / 2xl / 3xl / 4xl
25
+ extend the scale beyond the universal range for hero placeholder
26
+ contexts. `fill` matches the parent box. */
27
+ :scope[size="xs"] { --icon-size: 0.75rem; } /* 12px */
28
+ :scope[size="sm"] { --icon-size: 0.875rem; } /* 14px */
29
+ :scope[size="md"] { --icon-size: 1rem; } /* 16px */
30
+ :scope[size="lg"] { --icon-size: 1.25rem; } /* 20px */
31
+ :scope[size="xl"] { --icon-size: 2rem; } /* 32px */
32
+ :scope[size="2xl"] { --icon-size: 3rem; } /* 48px */
33
+ :scope[size="3xl"] { --icon-size: 4rem; } /* 64px */
34
+ :scope[size="4xl"] { --icon-size: 6rem; } /* 96px */
35
+ :scope[size="fill"] { --icon-size: 100%; }
20
36
  }
@@ -19,6 +19,24 @@ class AdiaIcon extends AdiaElement {
19
19
  if (this.label) this.setAttribute('aria-label', this.label);
20
20
  const svg = getIcon(this.name, this.weight || 'regular');
21
21
  if (svg && this.innerHTML !== svg) this.innerHTML = svg;
22
+
23
+ // Pixel / rem / em passthrough — `size="48"` → 48px,
24
+ // `size="3.5rem"` → 3.5rem. Named-scale values (xs/sm/md/lg/xl/
25
+ // 2xl/3xl/4xl/fill) are handled by `:scope[size="…"]` rules in
26
+ // icon.css; we only set --icon-size inline for free-form values.
27
+ if (this.size && this.#isFreeFormSize(this.size)) {
28
+ this.style.setProperty('--icon-size', this.#normalizeSize(this.size));
29
+ } else if (this.style.getPropertyValue('--icon-size')) {
30
+ this.style.removeProperty('--icon-size');
31
+ }
32
+ }
33
+
34
+ #isFreeFormSize(s) {
35
+ return /^\d+(\.\d+)?(px|rem|em)?$/.test(s);
36
+ }
37
+
38
+ #normalizeSize(s) {
39
+ return /^\d+(\.\d+)?$/.test(s) ? `${s}px` : s;
22
40
  }
23
41
  }
24
42
  customElements.define('icon-ui', AdiaIcon);
@@ -18,8 +18,12 @@ props:
18
18
  default: ""
19
19
  size:
20
20
  description: >-
21
- Icon size. Accepts the named scale (xs/sm/md/lg/xl) or a pixel value
22
- as a string ("12", "16", "24", "32", "48"). Overrides --a-icon-size.
21
+ Icon size. Accepts the named scale
22
+ (`xs` 12px / `sm` 14px / `md` 16px / `lg` 20px / `xl` 32px /
23
+ `2xl` 48px / `3xl` 64px / `4xl` 96px / `fill` 100% of parent)
24
+ or a free-form pixel / rem / em value as a string ("48", "3rem",
25
+ "1.25em"). Overrides the inherited `--a-icon-size` from the
26
+ universal `[size]` system on ancestors.
23
27
  type: string
24
28
  default: ""
25
29
  weight:
@@ -15,6 +15,7 @@ export { AdiaInput } from './input/input.js';
15
15
  export { AdiaTextarea } from './textarea/textarea.js';
16
16
  export { AdiaCheck } from './check/check.js';
17
17
  export { AdiaRadio } from './radio/radio.js';
18
+ export { AdiaOptionCard } from './option-card/option-card.js';
18
19
  export { AdiaSwitch } from './switch/switch.js';
19
20
  export { AdiaSlider } from './slider/slider.js';
20
21
  export { AdiaSelect } from './select/select.js';
@@ -52,7 +52,7 @@
52
52
  "default": ""
53
53
  },
54
54
  "label": {
55
- "description": "Label text rendered above the input field",
55
+ "description": "Inline label rendered as a leading caption inside the input chrome, between any prefix and the value. Wires aria-labelledby on the editable surface. For stacked label / hint / error compositions, wrap with field-ui.",
56
56
  "type": "string",
57
57
  "default": ""
58
58
  },
@@ -11,11 +11,8 @@
11
11
  --input-height: var(--a-size);
12
12
  --input-px: var(--a-ui-px);
13
13
  --input-font-size: var(--a-ui-size);
14
- --input-label-size: var(--a-label-size);
15
- --input-label-fg: var(--a-label-color);
16
14
  --input-placeholder-fg: var(--a-ui-text-placeholder);
17
15
  --input-affix-fg: var(--a-ui-text-placeholder);
18
- --input-gap: var(--a-ui-py);
19
16
  --input-field-gap: var(--a-space-1);
20
17
 
21
18
  /* ── Transitions ── */
@@ -26,8 +23,7 @@
26
23
  --input-bg-hover: var(--a-ui-bg-hover);
27
24
  --input-fg-hover: var(--a-fg);
28
25
  --input-affix-fg-hover: var(--a-fg-subtle);
29
- --input-fg-focus: var(--a-fg);
30
- --input-label-fg-focus: var(--a-fg-subtle);
26
+ --input-fg-focus: var(--a-fg-strong);
31
27
 
32
28
  /* ── State: disabled ── */
33
29
  --input-bg-disabled: var(--a-ui-bg-disabled);
@@ -37,23 +33,20 @@
37
33
  :scope {
38
34
  /* ── Base ── */
39
35
  box-sizing: border-box;
40
- display: flex;
41
- flex-direction: column;
42
- gap: var(--input-gap);
43
- }
44
-
45
- :scope[data-direction="row"] {
46
- display: grid;
47
- grid-template-columns: 1fr 1fr;
48
- align-items: center;
36
+ display: block;
49
37
  }
50
38
 
51
- /* Label */
39
+ /* Inline label — sits inside [slot="field"] as a leading caption,
40
+ between [slot="prefix"] and [slot="text"]. Dim by default; shares
41
+ the input chrome border. For stacked label / hint / error, wrap
42
+ with field-ui. */
52
43
  [slot="label"] {
53
- font-size: var(--input-label-size);
54
- color: var(--input-label-fg);
44
+ flex-shrink: 0;
45
+ color: var(--input-affix-fg);
46
+ font-size: var(--input-font-size);
47
+ user-select: none;
48
+ pointer-events: none;
55
49
  }
56
- [slot="label"][label]::after { content: attr(label); }
57
50
 
58
51
  /* Field container */
59
52
  [slot="field"] {
@@ -68,7 +61,13 @@
68
61
  color: var(--input-fg);
69
62
  font: inherit;
70
63
  font-size: var(--input-font-size);
71
- line-height: 1;
64
+ /* line-height: 1.4 (not 1) so descender-bearing glyphs (g, j, p, q, y)
65
+ have room inside [slot="text"]'s line box. With line-height: 1 the
66
+ line box equals the em-square and descenders extend below it; the
67
+ overflow: hidden on [slot="text"] then clips them. The min-height
68
+ still controls overall chrome height — align-items: center keeps the
69
+ baseline centered regardless of line-height. */
70
+ line-height: 1.4;
72
71
  cursor: text;
73
72
  transition: border-color var(--input-duration) var(--input-easing);
74
73
  }
@@ -92,9 +91,6 @@
92
91
  :scope[error]:not([disabled]):focus-within [slot="field"] {
93
92
  box-shadow: var(--input-focus-ring-invalid);
94
93
  }
95
- :scope:not([disabled]):focus-within [slot="label"] {
96
- color: var(--input-label-fg-focus);
97
- }
98
94
 
99
95
  /* Text (contenteditable span) */
100
96
  [slot="text"] {
@@ -122,17 +118,26 @@
122
118
 
123
119
  /* Placeholder (contenteditable only) */
124
120
  span[slot="text"][data-empty]::before {
125
- content: attr(aria-placeholder);
121
+ content: attr(data-placeholder);
126
122
  color: var(--input-placeholder-fg);
127
123
  pointer-events: none;
128
124
  }
129
125
 
130
- /* Prefix + Suffix */
126
+ /* Prefix + Suffix — inline-flex so icon-ui (or any non-text affix
127
+ content) centers vertically within the slot wrapper. Without
128
+ this, an icon inside a default <span slot="prefix"> sits at the
129
+ baseline of the field's 1.4 line-box and renders visually below
130
+ the field's center. With inline-flex + align-items: center the
131
+ icon centers in the slot box, which is itself centered in the
132
+ field via the field's align-items: center. */
131
133
  [slot="prefix"],
132
134
  [slot="suffix"] {
135
+ display: inline-flex;
136
+ align-items: center;
133
137
  flex-shrink: 0;
134
138
  color: var(--input-affix-fg);
135
139
  font-size: var(--input-font-size);
140
+ line-height: 1;
136
141
  user-select: none;
137
142
  pointer-events: none;
138
143
  }
@@ -2,9 +2,17 @@
2
2
  * <input-ui> — Text input. The host IS the interactive surface.
3
3
  * Uses contenteditable for text entry, ElementInternals for form participation.
4
4
  *
5
- * Supports prefix/suffix slots:
6
- * <input-ui label="Weight" suffix="kg"></input-ui>
7
- * <input-ui placeholder="Search" prefix="search"></input-ui>
5
+ * Slots inside [slot="field"]:
6
+ * prefix label → text → suffix
7
+ *
8
+ * <input-ui label="Email" placeholder="you@acme.com"></input-ui>
9
+ * <input-ui label="Email" prefix="user" placeholder="you@acme.com"></input-ui>
10
+ * <input-ui placeholder="Search" prefix="magnifying-glass"></input-ui>
11
+ * <input-ui prefix="@" value="kim"></input-ui>
12
+ *
13
+ * label renders as a dim leading caption inside the chrome (next to the
14
+ * value, sharing the input's border) — for stacked label / hint / error
15
+ * compositions, wrap with field-ui.
8
16
  */
9
17
 
10
18
  import { AdiaFormElement } from '../../core/form.js';
@@ -15,6 +23,13 @@ const renderAffix = (v) => isIconName(v)
15
23
  : v;
16
24
 
17
25
  class AdiaInput extends AdiaFormElement {
26
+ // Opt out of AdiaFormElement's per-control `label` deprecation warning.
27
+ // input-ui's `label` is a first-class API rendering an inline-leading
28
+ // caption inside the chrome with `aria-labelledby` wiring on the
29
+ // editable surface — not the inert above-the-field rendering that
30
+ // motivated the deprecation.
31
+ static labelDeprecated = false;
32
+
18
33
  static properties = {
19
34
  ...AdiaFormElement.properties,
20
35
  placeholder: { type: String, default: '', reflect: true },
@@ -28,6 +43,8 @@ class AdiaInput extends AdiaFormElement {
28
43
  static template = () => null;
29
44
 
30
45
  #textEl = null;
46
+ #labelEl = null;
47
+ static #labelSeq = 0;
31
48
 
32
49
  get #isNativeInput() {
33
50
  return this.type === 'password' || this.type === 'number';
@@ -39,24 +56,28 @@ class AdiaInput extends AdiaFormElement {
39
56
 
40
57
  if (!this.querySelector('[slot="text"]')) {
41
58
  const useNative = this.#isNativeInput;
59
+ const labelId = this.label ? `input-label-${++AdiaInput.#labelSeq}` : '';
42
60
  this.innerHTML = `
43
- ${this.label ? `<label slot="label" label="${this.label}"></label>` : ''}
44
61
  <div slot="field">
45
62
  ${this.prefix ? `<span slot="prefix">${renderAffix(this.prefix)}</span>` : ''}
63
+ ${this.label ? `<span slot="label" id="${labelId}">${this.label}</span>` : ''}
46
64
  ${useNative
47
65
  ? `<input slot="text" type="${this.type}" tabindex="0"
48
66
  placeholder="${this.placeholder}" value="${this.value || ''}"
49
67
  autocomplete="${this.type === 'password' ? 'current-password' : 'off'}"
68
+ ${labelId ? `aria-labelledby="${labelId}"` : ''}
50
69
  ${this.disabled ? 'disabled' : ''} ${this.readonly ? 'readonly' : ''} />`
51
70
  : `<span slot="text" contenteditable="plaintext-only" tabindex="0"
52
71
  ${this.value ? '' : 'data-empty=""'}
53
- aria-placeholder="${this.placeholder}"></span>`}
72
+ ${labelId ? `aria-labelledby="${labelId}"` : ''}
73
+ data-placeholder="${this.placeholder}"></span>`}
54
74
  ${this.suffix ? `<span slot="suffix">${renderAffix(this.suffix)}</span>` : ''}
55
75
  </div>
56
76
  `;
57
77
  }
58
78
 
59
79
  this.#textEl = this.querySelector('[slot="text"]');
80
+ this.#labelEl = this.querySelector('[slot="label"]');
60
81
  if (!this.#isNativeInput && this.value) this.#textEl.textContent = this.value;
61
82
 
62
83
  if (this.#textEl) {
@@ -75,7 +96,7 @@ class AdiaInput extends AdiaFormElement {
75
96
  this.#textEl.disabled = this.disabled;
76
97
  this.#textEl.readOnly = this.readonly;
77
98
  } else {
78
- this.#textEl.setAttribute('aria-placeholder', this.placeholder);
99
+ this.#textEl.setAttribute('data-placeholder', this.placeholder);
79
100
  if (this.disabled || this.readonly) {
80
101
  this.#textEl.contentEditable = 'false';
81
102
  } else {
@@ -83,10 +104,15 @@ class AdiaInput extends AdiaFormElement {
83
104
  }
84
105
  }
85
106
 
86
- const label = this.querySelector('[slot="label"]');
87
- if (label && this.label) label.setAttribute('label', this.label);
107
+ if (this.#labelEl) this.#labelEl.textContent = this.label || '';
88
108
 
89
- this.setAttribute('aria-label', this.label || this.placeholder || '');
109
+ if (this.label) {
110
+ this.removeAttribute('aria-label');
111
+ } else if (this.placeholder) {
112
+ this.setAttribute('aria-label', this.placeholder);
113
+ } else {
114
+ this.removeAttribute('aria-label');
115
+ }
90
116
  }
91
117
 
92
118
  #onInput = () => {
@@ -141,6 +167,7 @@ class AdiaInput extends AdiaFormElement {
141
167
  this.#textEl.removeEventListener('paste', this.#onPaste);
142
168
  }
143
169
  this.#textEl = null;
170
+ this.#labelEl = null;
144
171
  }
145
172
  }
146
173
  customElements.define('input-ui', AdiaInput);
@@ -47,7 +47,9 @@ props:
47
47
  type: string
48
48
  default: ""
49
49
  label:
50
- description: Label text rendered above the input field
50
+ description: Inline label rendered as a leading caption inside the input chrome,
51
+ between any prefix and the value. Wires aria-labelledby on the editable
52
+ surface. For stacked label / hint / error compositions, wrap with field-ui.
51
53
  type: string
52
54
  default: ""
53
55
  maxlength:
@@ -0,0 +1,262 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/OptionCard.json",
4
+ "title": "OptionCard",
5
+ "description": "Selectable card with radio semantics. A \"rich radio\" — single-select one of N where each option carries a heading, optional description, and optional leading icon. Siblings sharing a `name` form a radiogroup. The whole card is the click target; a CSS-rendered radio circle in the top-left signals state. Form-associated, so `name=value` submits with the parent form when checked.",
6
+ "type": "object",
7
+ "allOf": [
8
+ {
9
+ "$ref": "common_types.json#/$defs/ComponentCommon"
10
+ },
11
+ {
12
+ "$ref": "common_types.json#/$defs/CatalogComponentCommon"
13
+ }
14
+ ],
15
+ "properties": {
16
+ "description": {
17
+ "description": "Description text. Stamped into a [slot=\"description\"] span when no slotted description is provided.",
18
+ "type": "string",
19
+ "default": ""
20
+ },
21
+ "required": {
22
+ "description": "Marks the radiogroup as requiring a selection for form validation.",
23
+ "type": "boolean",
24
+ "default": false
25
+ },
26
+ "checked": {
27
+ "description": "Whether this card is currently selected.",
28
+ "type": "boolean",
29
+ "default": false
30
+ },
31
+ "component": {
32
+ "const": "OptionCard"
33
+ },
34
+ "disabled": {
35
+ "description": "Disables interaction and dims the card.",
36
+ "type": "boolean",
37
+ "default": false
38
+ },
39
+ "heading": {
40
+ "description": "Heading text. Stamped into a [slot=\"heading\"] span when no slotted heading is provided.",
41
+ "type": "string",
42
+ "default": ""
43
+ },
44
+ "icon": {
45
+ "description": "Optional Phosphor icon name. Stamped as a leading <icon-ui slot=\"icon\"> when set.",
46
+ "type": "string",
47
+ "default": ""
48
+ },
49
+ "layout": {
50
+ "description": "Internal layout. `default` puts the indicator on the left and the icon adjacent. `tile` stacks vertically — icon top-left, indicator top-right, heading + description below — for hero pickers (source / role / plan tiles) where the icon is a primary brand cue.",
51
+ "type": "string",
52
+ "enum": [
53
+ "default",
54
+ "tile"
55
+ ],
56
+ "default": "default"
57
+ },
58
+ "name": {
59
+ "description": "Form control name. Siblings sharing a name form a radiogroup.",
60
+ "type": "string",
61
+ "default": ""
62
+ },
63
+ "value": {
64
+ "description": "Form value submitted when checked (defaults to `on` if empty).",
65
+ "type": "string",
66
+ "default": ""
67
+ }
68
+ },
69
+ "required": [
70
+ "component"
71
+ ],
72
+ "unevaluatedProperties": false,
73
+ "x-adiaui": {
74
+ "anti_patterns": [],
75
+ "category": "input",
76
+ "events": {
77
+ "change": {
78
+ "description": "Fired when this card becomes selected (bubbles)."
79
+ }
80
+ },
81
+ "examples": [
82
+ {
83
+ "description": "A four-option pick-one for \"what brings you here\" — radio-card behavior with heading + description per option.",
84
+ "a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"Column\",\n \"gap\": \"2\",\n \"children\": [\"build\", \"explore\", \"migrate\", \"evaluate\"]\n },\n {\n \"id\": \"build\",\n \"component\": \"OptionCard\",\n \"name\": \"use-case\",\n \"value\": \"build\",\n \"checked\": true,\n \"heading\": \"I'm building a product\",\n \"description\": \"Spinning up a new project — design, ship, iterate.\"\n },\n {\n \"id\": \"explore\",\n \"component\": \"OptionCard\",\n \"name\": \"use-case\",\n \"value\": \"explore\",\n \"heading\": \"I'm exploring the product\",\n \"description\": \"Kicking the tires before bringing a team along.\"\n },\n {\n \"id\": \"migrate\",\n \"component\": \"OptionCard\",\n \"name\": \"use-case\",\n \"value\": \"migrate\",\n \"heading\": \"I'm migrating from another tool\",\n \"description\": \"Moving an existing workspace and want a smooth port.\"\n },\n {\n \"id\": \"evaluate\",\n \"component\": \"OptionCard\",\n \"name\": \"use-case\",\n \"value\": \"evaluate\",\n \"heading\": \"I'm evaluating for my team\",\n \"description\": \"Comparing options and want to dig into specifics.\"\n }\n]",
85
+ "name": "use-case-picker"
86
+ }
87
+ ],
88
+ "keywords": [
89
+ "option",
90
+ "card",
91
+ "radio",
92
+ "select",
93
+ "choice",
94
+ "picker",
95
+ "tier",
96
+ "plan",
97
+ "onboarding",
98
+ "registration"
99
+ ],
100
+ "name": "AdiaOptionCard",
101
+ "related": [
102
+ "radio",
103
+ "card",
104
+ "check",
105
+ "segmented"
106
+ ],
107
+ "slots": {
108
+ "description": {
109
+ "description": "Rich description content. Overrides the `description` attribute when present."
110
+ },
111
+ "default": {
112
+ "description": "Spillover content revealed only when the card is checked — typically a follow-up form field (e.g. a textarea on an \"Other\" option, conditional inputs that depend on the selection). Aligns with the heading/description column; hidden via `display: none` when not checked."
113
+ },
114
+ "heading": {
115
+ "description": "Rich heading content. Overrides the `heading` attribute when present."
116
+ },
117
+ "icon": {
118
+ "description": "Custom icon element. Overrides the `icon` attribute when present."
119
+ }
120
+ },
121
+ "states": [
122
+ {
123
+ "description": "Default, ready for interaction.",
124
+ "name": "idle"
125
+ },
126
+ {
127
+ "description": "Pointer over a non-checked card.",
128
+ "name": "hover",
129
+ "selector": ":not([checked]):not([disabled]):hover"
130
+ },
131
+ {
132
+ "description": "Selected — accent border, tinted background, filled radio circle.",
133
+ "attribute": "checked",
134
+ "name": "checked"
135
+ },
136
+ {
137
+ "description": "Non-interactive; dimmed.",
138
+ "attribute": "disabled",
139
+ "name": "disabled"
140
+ },
141
+ {
142
+ "description": "Keyboard focus ring.",
143
+ "name": "focused",
144
+ "selector": ":focus-visible"
145
+ }
146
+ ],
147
+ "synonyms": {
148
+ "picker": [
149
+ "option-card",
150
+ "radio",
151
+ "select"
152
+ ],
153
+ "radio": [
154
+ "option-card",
155
+ "radio",
156
+ "select"
157
+ ],
158
+ "tier": [
159
+ "option-card",
160
+ "card",
161
+ "plan"
162
+ ]
163
+ },
164
+ "tag": "option-card-ui",
165
+ "tokens": {
166
+ "--option-card-bg": {
167
+ "description": "Default background."
168
+ },
169
+ "--option-card-bg-checked": {
170
+ "description": "Background when checked."
171
+ },
172
+ "--option-card-bg-hover": {
173
+ "description": "Hover background (non-checked)."
174
+ },
175
+ "--option-card-border": {
176
+ "description": "Default border color."
177
+ },
178
+ "--option-card-border-checked": {
179
+ "description": "Border color when checked."
180
+ },
181
+ "--option-card-border-hover": {
182
+ "description": "Hover border color (non-checked)."
183
+ },
184
+ "--option-card-desc-color": {
185
+ "description": "Description text color."
186
+ },
187
+ "--option-card-desc-line-height": {
188
+ "description": "Description line height."
189
+ },
190
+ "--option-card-desc-size": {
191
+ "description": "Description font size."
192
+ },
193
+ "--option-card-disabled-opacity": {
194
+ "description": "Opacity multiplier when disabled."
195
+ },
196
+ "--option-card-duration": {
197
+ "description": "Transition duration for hover / checked state changes."
198
+ },
199
+ "--option-card-easing": {
200
+ "description": "Transition easing."
201
+ },
202
+ "--option-card-focus-ring": {
203
+ "description": "Focus ring (box-shadow value)."
204
+ },
205
+ "--option-card-gap-x": {
206
+ "description": "Horizontal gap between indicator (and icon) and content."
207
+ },
208
+ "--option-card-gap-y": {
209
+ "description": "Vertical gap between heading and description."
210
+ },
211
+ "--option-card-heading-color": {
212
+ "description": "Heading text color."
213
+ },
214
+ "--option-card-heading-color-checked": {
215
+ "description": "Heading text color when the card is checked (defaults to `--a-fg-strong` so the selected card reads with extra emphasis on top of the bg/border state)."
216
+ },
217
+ "--option-card-heading-size": {
218
+ "description": "Heading font size."
219
+ },
220
+ "--option-card-heading-weight": {
221
+ "description": "Heading font weight."
222
+ },
223
+ "--option-card-icon-color": {
224
+ "description": "Leading icon color when the card is not checked."
225
+ },
226
+ "--option-card-icon-color-checked": {
227
+ "description": "Leading icon color when the card is checked."
228
+ },
229
+ "--option-card-icon-size": {
230
+ "description": "Leading icon size (sets `--a-icon-size` on the slotted icon-ui)."
231
+ },
232
+ "--option-card-padding-block": {
233
+ "description": "Vertical padding inside the card."
234
+ },
235
+ "--option-card-padding-inline": {
236
+ "description": "Horizontal padding inside the card."
237
+ },
238
+ "--option-card-radio-bg": {
239
+ "description": "Indicator background when not checked."
240
+ },
241
+ "--option-card-radio-border": {
242
+ "description": "Indicator border when not checked."
243
+ },
244
+ "--option-card-radio-dot": {
245
+ "description": "Inner dot color when checked."
246
+ },
247
+ "--option-card-radio-fill": {
248
+ "description": "Indicator fill color when checked."
249
+ },
250
+ "--option-card-radio-size": {
251
+ "description": "Diameter of the indicator circle."
252
+ },
253
+ "--option-card-radius": {
254
+ "description": "Card corner radius."
255
+ }
256
+ },
257
+ "traits": [
258
+ "focusable"
259
+ ],
260
+ "version": 1
261
+ }
262
+ }