bestax-mcp 1.14.3 → 1.14.4

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/data/catalog.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 2,
3
3
  "generatedFrom": {
4
4
  "package": "@allxsmith/bestax-bulma",
5
- "version": "5.27.2"
5
+ "version": "5.27.8"
6
6
  },
7
7
  "docsBase": "https://bestax.io/docs",
8
8
  "categories": [
@@ -917,7 +917,7 @@
917
917
  "import": "import { Slider } from '@allxsmith/bestax-bulma';",
918
918
  "compound": false,
919
919
  "propCount": 30,
920
- "exampleCount": 11
920
+ "exampleCount": 13
921
921
  },
922
922
  {
923
923
  "name": "Span",
@@ -2007,6 +2007,7 @@
2007
2007
  "--bulma-notification-background-l": ["Notification"],
2008
2008
  "--bulma-notification-code-background-color": ["Notification"],
2009
2009
  "--bulma-notification-color-l": ["Notification"],
2010
+ "--bulma-notification-delete-padding-inline-end": ["Notification"],
2010
2011
  "--bulma-notification-h": ["Notification"],
2011
2012
  "--bulma-notification-padding": ["Notification"],
2012
2013
  "--bulma-notification-radius": ["Notification"],
@@ -47,14 +47,14 @@
47
47
  },
48
48
  {
49
49
  "title": "Context-Aware Rendering",
50
- "code": "function example() {\n return (\n <Field horizontal label=\"Fruit\" labelProps={{ htmlFor: 'fruit-field' }}>\n <Field.Body>\n <Field>\n <Autocomplete\n id=\"fruit-field\"\n data={['Apple', 'Banana', 'Cherry']}\n placeholder=\"Search fruit...\"\n />\n </Field>\n </Field.Body>\n </Field>\n );\n}"
50
+ "code": "function example() {\n return (\n <Field\n horizontal\n label=\"Fruit\"\n labelProps={{ htmlFor: 'fruit-field', id: 'fruit-field-label' }}\n >\n <Field.Body>\n <Field>\n <Autocomplete\n id=\"fruit-field\"\n data={['Apple', 'Banana', 'Cherry']}\n placeholder=\"Search fruit...\"\n />\n </Field>\n </Field.Body>\n </Field>\n );\n}"
51
51
  },
52
52
  {
53
53
  "title": "Context-Aware Rendering",
54
- "code": "function example() {\n return (\n <Field\n horizontal\n label=\"Fruit\"\n labelProps={{ htmlFor: 'fruit-field-control' }}\n >\n <Field.Body>\n <Field>\n <Control iconLeftName=\"search\">\n <Autocomplete\n id=\"fruit-field-control\"\n data={['Apple', 'Banana', 'Cherry']}\n placeholder=\"Search fruit...\"\n />\n </Control>\n </Field>\n </Field.Body>\n </Field>\n );\n}"
54
+ "code": "function example() {\n return (\n <Field\n horizontal\n label=\"Fruit\"\n labelProps={{\n htmlFor: 'fruit-field-control',\n id: 'fruit-field-control-label',\n }}\n >\n <Field.Body>\n <Field>\n <Control iconLeftName=\"search\">\n <Autocomplete\n id=\"fruit-field-control\"\n data={['Apple', 'Banana', 'Cherry']}\n placeholder=\"Search fruit...\"\n />\n </Control>\n </Field>\n </Field.Body>\n </Field>\n );\n}"
55
55
  }
56
56
  ],
57
- "accessibility": "- The `label` prop is automatically associated with the inner text input (`htmlFor` plus a generated `id`, or your own `id` if you pass one), so clicking the label focuses the input and assistive technology announces it. Inside a labeled `Field`, that `Field`'s label names the input the same way.\n- A user-supplied `id` is applied to the inner input (the labellable control), not the wrapper div.\n- Uses `role=\"combobox\"` with `aria-expanded`, which is true only while the list is on screen\n- Has `aria-haspopup=\"listbox\"` and `aria-autocomplete=\"list\"`, and, while the list is on screen, `aria-controls` pointing at it\n- Focus stays in the input, which points `aria-activedescendant` at the highlighted option as the arrow keys move it\n- The list is a `role=\"listbox\"` named by the `label` prop's label. Inside an outer `Field`, or with no `label`, it falls back to `aria-label=\"Suggestions\"`\n- Dropdown items use `role=\"option\"` with `aria-selected`\n- Disabled items have `aria-disabled`\n- The `clearable` clear button is a native button named \"Clear\": `Tab` from the input reaches it, and `Enter` or `Space` clears the input and puts focus back in it\n- Full keyboard navigation support",
57
+ "accessibility": "- The `label` prop is automatically associated with the inner text input (`htmlFor` plus a generated `id`, or your own `id` if you pass one), so clicking the label focuses the input and assistive technology announces it. Inside a labeled `Field`, that `Field`'s label names the input instead when it points at it: a `Field` that is not `grouped` or `hasAddons` points it there on its own when the Autocomplete has no `id` and no inner `Field` sits between them, and a label you wire by hand with `labelProps.htmlFor` points at the input with that `id`.\n- A user-supplied `id` is applied to the inner input (the labellable control), not the wrapper div.\n- Uses `role=\"combobox\"` with `aria-expanded`, which is true only while the list is on screen\n- Has `aria-haspopup=\"listbox\"` and `aria-autocomplete=\"list\"`, and, while the list is on screen, `aria-controls` pointing at it\n- Focus stays in the input, which points `aria-activedescendant` at the highlighted option as the arrow keys move it\n- The list is a `role=\"listbox\"` named through `aria-labelledby` by the label that names the input, when that label has an id: the `label` prop's label always has one, and so does a `Field`'s label that the `Field` wires itself. A `Field` label you wire by hand with `labelProps.htmlFor`, which is how a horizontal form names an Autocomplete in an inner `Field`, needs an `id` in its `labelProps` as well. Otherwise the list falls back to `aria-label=\"Suggestions\"`\n- Dropdown items use `role=\"option\"` with `aria-selected`\n- Disabled items have `aria-disabled`\n- The `clearable` clear button is a native button named \"Clear\": `Tab` from the input reaches it, and `Enter` or `Space` clears the input and puts focus back in it\n- Full keyboard navigation support",
58
58
  "related": ["Input", "Select", "Taginput"],
59
59
  "storybook": "https://bestax.io/storybook/?path=/story/form-autocomplete",
60
60
  "summary": "The `Autocomplete` component provides an input field with dropdown suggestions that filter based on user input.",
@@ -71,7 +71,7 @@
71
71
  "name": "label",
72
72
  "type": "React.ReactNode",
73
73
  "default": null,
74
- "description": "Field label. Automatically associated with the text input via `htmlFor` — uses your `id` when provided, otherwise a generated one. Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`).",
74
+ "description": "Field label, associated with the text input via `htmlFor`: through your `id` when provided, otherwise a generated one. Dropped inside an outer `Field`, whose label names the input instead when it points at it. The `Field` points its label there itself when the Autocomplete has no `id` and no inner `Field` sits between them (and the `Field` is not `grouped` or `hasAddons`); a label you wire by hand with `labelProps.htmlFor` points at the input with that `id`. The label naming the input also names the open suggestion list, through `aria-labelledby`, when it has an id: this label always does, and an outer `Field`'s does unless you wired it by hand without an `id` in its `labelProps`. Otherwise the list is named \"Suggestions\".",
75
75
  "required": false,
76
76
  "inherited": false,
77
77
  "deprecated": false
@@ -80,7 +80,7 @@
80
80
  "name": "labelProps",
81
81
  "type": "React.LabelHTMLAttributes<HTMLLabelElement> & { [key: string]: unknown; }",
82
82
  "default": null,
83
- "description": "Props for the label element. An explicit `htmlFor` here overrides the automatic association (no id is generated then).",
83
+ "description": "Props for the label element. An explicit `htmlFor` here overrides the automatic association (the input then gets no generated id). While the label names the input, the suggestion list's `aria-labelledby` points at it, through an `id` here when you give one and a generated one otherwise.",
84
84
  "required": false,
85
85
  "inherited": false,
86
86
  "deprecated": false
@@ -191,7 +191,7 @@
191
191
  "name": "label",
192
192
  "type": "React.ReactNode",
193
193
  "default": null,
194
- "description": "Field label (component auto-wraps in a `Field` if not already inside). Automatically associated with the input via `htmlFor` — uses your `id` when provided, otherwise a generated one. Not wired in `inline` mode (no visible input to label). Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`).",
194
+ "description": "Field label (component auto-wraps in a `Field` if not already inside). Automatically associated with the input via `htmlFor` — uses your `id` when provided, otherwise a generated one. Not wired in `inline` mode (no visible input to label). Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`) and you set no `id` here.",
195
195
  "required": false,
196
196
  "inherited": false,
197
197
  "deprecated": false
@@ -46,7 +46,7 @@
46
46
  "code": "<Field label=\"Leave\">\n <Control iconLeftName=\"calendar-alt\">\n <DateRangeInput />\n </Control>\n</Field>"
47
47
  }
48
48
  ],
49
- "accessibility": "- The root is a `role=\"group\"` named by `label` through `aria-labelledby`, since the label names two inputs rather than one. Inside a labeled `Field`, that `Field`'s label names the group the same way. An `aria-label` or `aria-labelledby` you give the group wins over either label.\n- Each input is a `role=\"combobox\"` with `aria-haspopup=\"dialog\"`, `aria-expanded` and `aria-controls`, and its own name: \"Start date\" or \"End date\", from the `labels` keys `rangeStart` and `rangeEnd`. The separator between them is hidden from assistive technology.\n- The popover is a `role=\"dialog\"` named \"Choose date range\" (`labels.chooseDateRange`). Opening it puts focus on the calendar's focused day, and closing it returns focus to the input you were last in, or to the start input.\n- The day grid is `aria-multiselectable`, and its days sit in a `role=\"row\"` per week. Every day of the range is `aria-selected`, both ends included. While the end is still being picked, only the start is: the preview is not a selection.\n- The start and end days are described as \"Start date\" and \"End date\", so moving onto either says which it is. While the end is still being picked, the day the range would end on is described as \"Choose as end date\" (`labels.rangePreviewEnd`).\n- A polite live region in the calendar states the range in words, such as \"Start date: June 10, 2024, End date: June 13, 2024\", and announces each pick. The grid points at it too, so moving into the grid reads the current range.\n- Everything else is the calendar `DateInput` uses: a roving `tabindex`, `aria-disabled` on disabled days and `aria-current=\"date\"` on today.",
49
+ "accessibility": "- The root is a `role=\"group\"` named by `label` through `aria-labelledby`, since the label names two inputs rather than one. Inside a labeled `Field`, that `Field`'s label names the group the same way, whether the `Field` holds `DateRangeInput` or a `DateRangeInputBase` you composed in a `Control` yourself, `inline` or not. An `aria-label` or `aria-labelledby` you give the group wins over either label.\n- Each input is a `role=\"combobox\"` with `aria-haspopup=\"dialog\"`, `aria-expanded` and `aria-controls`, and its own name: \"Start date\" or \"End date\", from the `labels` keys `rangeStart` and `rangeEnd`. The separator between them is hidden from assistive technology.\n- The popover is a `role=\"dialog\"` named \"Choose date range\" (`labels.chooseDateRange`). Opening it puts focus on the calendar's focused day, and closing it returns focus to the input you were last in, or to the start input.\n- The day grid is `aria-multiselectable`, and its days sit in a `role=\"row\"` per week. Every day of the range is `aria-selected`, both ends included. While the end is still being picked, only the start is: the preview is not a selection.\n- The start and end days are described as \"Start date\" and \"End date\", so moving onto either says which it is. While the end is still being picked, the day the range would end on is described as \"Choose as end date\" (`labels.rangePreviewEnd`).\n- A polite live region in the calendar states the range in words, such as \"Start date: June 10, 2024, End date: June 13, 2024\", and announces each pick. The grid points at it too, so moving into the grid reads the current range.\n- Everything else is the calendar `DateInput` uses: a roving `tabindex`, `aria-disabled` on disabled days and `aria-current=\"date\"` on today.",
50
50
  "related": ["DateInput", "DateTimeInput", "Field"],
51
51
  "storybook": "https://bestax.io/storybook/?path=/story/form-daterangeinput",
52
52
  "summary": "The `DateRangeInput` component is a form input for a start and end date, picked from one popover calendar or typed into two segmented inputs.",
@@ -187,7 +187,7 @@
187
187
  "name": "label",
188
188
  "type": "React.ReactNode",
189
189
  "default": null,
190
- "description": "Field label. Automatically associated with the input via `htmlFor` — uses your `id` when provided, otherwise a generated one. Not wired in `inline` mode (no visible input to label). Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`).",
190
+ "description": "Field label. Automatically associated with the input via `htmlFor` — uses your `id` when provided, otherwise a generated one. Not wired in `inline` mode (no visible input to label). Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`) and you set no `id` here.",
191
191
  "required": false,
192
192
  "inherited": false,
193
193
  "deprecated": false
@@ -129,25 +129,25 @@
129
129
  "css": "--bulma-delete-dimensions",
130
130
  "sass": null,
131
131
  "default": "1.25rem",
132
- "scope": "global"
132
+ "scope": "compound"
133
133
  },
134
134
  {
135
135
  "css": "--bulma-delete-background-l",
136
136
  "sass": null,
137
137
  "default": "0%",
138
- "scope": "global"
138
+ "scope": "root"
139
139
  },
140
140
  {
141
141
  "css": "--bulma-delete-background-alpha",
142
142
  "sass": null,
143
143
  "default": "0.5",
144
- "scope": "global"
144
+ "scope": "root"
145
145
  },
146
146
  {
147
147
  "css": "--bulma-delete-color",
148
148
  "sass": null,
149
149
  "default": "var(--bulma-white)",
150
- "scope": "global"
150
+ "scope": "root"
151
151
  }
152
152
  ]
153
153
  }
@@ -83,10 +83,10 @@
83
83
  },
84
84
  {
85
85
  "title": "Compound (dot-notation) usage",
86
- "code": "<Field horizontal>\n <Field.Label>Name</Field.Label>\n <Field.Body>\n <Field.Control>\n <Input placeholder=\"Jane Doe\" />\n </Field.Control>\n </Field.Body>\n</Field>"
86
+ "code": "<Field horizontal>\n <Field.Label size=\"normal\">\n <label className=\"label\" htmlFor=\"compound-name\">\n Name\n </label>\n </Field.Label>\n <Field.Body>\n <Field.Control>\n <Input id=\"compound-name\" placeholder=\"Jane Doe\" />\n </Field.Control>\n </Field.Body>\n</Field>"
87
87
  }
88
88
  ],
89
- "accessibility": "- The `label` prop names the one control the Field holds. A composed `InputBase`, `SelectBase`, or `TextAreaBase`, or a bestax input that renders a single input of its own (`Input`, `Select`, `TextArea`, `Numberinput`, `Slider`, `DateInput`, `TimeInput`, `DateTimeInput`, `Autocomplete`, `Taginput`, `File`), adopts a generated id the label's `for` points at, so clicking the label focuses the control and assistive technology announces it.\n- A group (`Radios`, `Checkboxes`, `Rate`, `DateRangeInput`) can't take a `for`, so the label gets an id (yours from `labelProps.id`, otherwise a generated one) and the group points `aria-labelledby` at it. An `aria-label` or `aria-labelledby` you set on the group wins over the Field's label, as it does over a group's own `label`.\n- An `id` you set on the control wins too: it keeps that id, and the label then names it only if you point `labelProps={{ htmlFor }}` at it. Pass `labelProps={{ htmlFor: undefined }}` to opt out of the association entirely.\n- Association is skipped for `grouped`/`hasAddons` fields (they hold several controls), and a `Field` nested inside a labeled one starts its own scope. A horizontal Field wraps its children in a `Field.Body` of its own and keeps the association, but the pattern with an inner `Field` in that body needs it wired by hand.\n- Two controls in one plain labeled Field would both adopt its id. Give each an `id` of its own and label it individually.\n- A labeled Field whose content takes no `for`, a group included, renders its `for` unmatched. `Checkbox`, `Radio` and `Switch` take nothing from the label at all: each is named by its own children, so put the text there rather than in the Field's `label`. Where you point content at the label by hand, as with a group in an inner `Field`, pass `labelProps={{ id, htmlFor: undefined }}` so the label keeps its id and drops the `for`.\n- Grouped/horizontal layouts use Bulma’s grid for layout.\n- Always use the `label` prop or a custom label for clarity.",
89
+ "accessibility": "- For a single control, prefer the control's own `label` prop, with `horizontal` for a side-by-side row. It renders this `Field` and its label and wires the `for` itself, to your `id` when you pass one: `<Input label=\"Email\" id=\"email\" horizontal />`.\n- The `label` prop names the one control the Field holds. A composed `InputBase`, `SelectBase`, `TextAreaBase`, `DateInputBase`, `TimeInputBase`, or `DateTimeInputBase`, or a bestax input that renders a single input of its own (`Input`, `Select`, `TextArea`, `Numberinput`, `Slider`, `DateInput`, `TimeInput`, `DateTimeInput`, `Autocomplete`, `Taginput`, `File`), adopts a generated id the label's `for` points at, so clicking the label focuses the control and assistive technology announces it. An `inline` picker renders no input, so it takes nothing.\n- A control with an `id` of its own keeps it and never takes the generated one, so `<Field label=\"Email\"><Input id=\"email\" /></Field>` renders a label that names nothing. Leave the `id` off, or pass the same value as `labelProps={{ htmlFor: 'email' }}`. Pass `labelProps={{ htmlFor: undefined }}` to opt out of the association entirely.\n- A range `Slider` takes that id on its low thumb, and its thumbs also point `aria-labelledby` at the label (at `labelProps.id` when you set one), so under a label of \"Price range\" they announce as \"Price range Minimum value\" and \"Price range Maximum value\". An `ariaLabel` entry names its thumb instead, and an `aria-label` or `aria-labelledby` on the Slider takes the label's place in both names. A label you wire by hand with `labelProps={{ htmlFor }}` gets no generated id, so it reaches the thumbs' names only when you give it an `id` there too, as in `labelProps={{ htmlFor: 'price', id: 'price-label' }}`.\n- An `Autocomplete` points its open suggestion list's `aria-labelledby` at the label the same way, so under a label of \"Country\" the list announces as \"Country\" rather than its fallback name, \"Suggestions\". A label you wire by hand needs an `id` in `labelProps` for this too, as in `labelProps={{ htmlFor: 'country', id: 'country-label' }}`.\n- A group (`Radios`, `Checkboxes`, `Rate`, `DateRangeInput`, or a composed `DateRangeInputBase`, `inline` or not) can't take a `for`, so the label gets an id (yours from `labelProps.id`, otherwise a generated one) and the group points `aria-labelledby` at it. An `aria-label` or `aria-labelledby` you set on the group wins over the Field's label, as it does over a group's own `label`.\n- Association is skipped for `grouped`/`hasAddons` fields (they hold several controls), and a `Field` nested inside a labeled one starts its own scope. A horizontal Field wraps its children in a `Field.Body` of its own and keeps the association, but the pattern with an inner `Field` in that body needs it wired by hand. A label you wire by hand names the control its `for` points at in a `grouped` or `hasAddons` field too, and with an `id` in `labelProps` it names a range `Slider`'s thumbs and an `Autocomplete`'s list there as well. No group points at it on its own.\n- Two controls in one plain labeled Field would both adopt its id. Give each an `id` of its own and label it individually.\n- Once a labeled Field's content has mounted, its label keeps the generated `for` only while one of the inputs above holds the id, and drops it over anything else: a group, a `Checkbox`, `Radio` or `Switch`, an `inline` picker, an inner `Field`, an input with an `id` of its own, markup of your own, or nothing at all. A server render, and the first render while hydrating, still write the `for`, so hydration matches, and an input that mounts later puts it back. A `for` you set with `labelProps={{ htmlFor }}` always stays. `Checkbox`, `Radio` and `Switch` take nothing from the label on their own: each is named by its own children, so put the text there, and the label names one only when you wire it as you would any control with an `id` of its own, `labelProps={{ htmlFor }}` plus that control's `id`. Where you point content at the label by hand, as with a group in an inner `Field`, `labelProps={{ id }}` is enough in the browser, and `labelProps={{ id, htmlFor: undefined }}` keeps the `for` out of server-rendered HTML too.\n- Grouped/horizontal layouts use Bulma’s grid for layout.",
90
90
  "related": [
91
91
  "Checkbox",
92
92
  "Checkboxes",
@@ -149,7 +149,7 @@
149
149
  "name": "label",
150
150
  "type": "React.ReactNode",
151
151
  "default": null,
152
- "description": "Field label, rendered above the widget. Automatically names the one control the Field holds: a composed `InputBase`, `SelectBase` or `TextAreaBase`, or a bestax input that renders a single input of its own (`Input`, `Select`, `TextArea`, `Numberinput`, `Slider`, `DateInput`, `TimeInput`, `DateTimeInput`, `Autocomplete`, `Taginput`, `File`), adopts a generated id that the label's `htmlFor` points at, and a group (`Radios`, `Checkboxes`, `Rate`, `DateRangeInput`) points `aria-labelledby` at the label's own id unless you gave the group an `aria-label` or `aria-labelledby`. Nothing else takes the label: `Checkbox`, `Radio` and `Switch` are named by their own children. Only the single inputs take the `htmlFor`, so with a group or any other content it matches nothing. Pass `labelProps={{ htmlFor }}` to wire your own `id`, or `labelProps={{ htmlFor: undefined }}` to opt out. Skipped for `grouped`/`hasAddons` fields (multiple controls), and a nested `Field` starts its own scope, so a horizontal Field whose body holds an inner `Field` names the control there only when you wire it. Two controls in one plain labeled Field would both adopt the id, so give each an `id` of its own.",
152
+ "description": "Field label, rendered above the widget. Automatically names the one control the Field holds: a composed `InputBase`, `SelectBase`, `TextAreaBase`, `DateInputBase`, `TimeInputBase` or `DateTimeInputBase` (an `inline` picker has no input, so it takes nothing), or a bestax input that renders a single input of its own (`Input`, `Select`, `TextArea`, `Numberinput`, `Slider`, `DateInput`, `TimeInput`, `DateTimeInput`, `Autocomplete`, `Taginput`, `File`), adopts a generated id that the label's `htmlFor` points at, and a group (`Radios`, `Checkboxes`, `Rate`, `DateRangeInput`, or a composed `DateRangeInputBase`, `inline` or not) points `aria-labelledby` at the label's own id unless you gave the group an `aria-label` or `aria-labelledby`. A range `Slider` takes the id on its low thumb and also starts each thumb's name with the label through `aria-labelledby`, unless its `ariaLabel` names that thumb or its own `aria-label` or `aria-labelledby` takes the label's place. An `Autocomplete` also names its open suggestion list after the label through `aria-labelledby`. Nothing else takes the label: `Checkbox`, `Radio` and `Switch` are named by their own children, and the label names one only when you wire it as you would any control with an `id` of its own, `labelProps={{ htmlFor }}` plus that control's `id`. Only the single inputs take the `htmlFor`, so once the Field's content has mounted the label keeps it only while one of them holds the id, and drops it over anything else: a group, a `Checkbox`, `Radio` or `Switch`, an `inline` picker, an inner `Field`, an input with an `id` of its own, markup of your own, or nothing at all. A server render, and the first render while hydrating, still write the `htmlFor`, which keeps hydration matching, and an input that mounts later puts it back. The label names an input with an `id` of your own only through `labelProps={{ htmlFor }}` set to that `id`, a `for` it always keeps; `labelProps={{ htmlFor: undefined }}` opts out. Skipped for `grouped`/`hasAddons` fields (multiple controls), and a nested `Field` starts its own scope, so a horizontal Field whose body holds an inner `Field` names the control there only when you wire it. Two controls in one plain labeled Field would both adopt the id, so give each an `id` of its own.",
153
153
  "required": false,
154
154
  "inherited": false,
155
155
  "deprecated": false
@@ -167,7 +167,7 @@
167
167
  "name": "labelProps",
168
168
  "type": "React.LabelHTMLAttributes<HTMLLabelElement> & { [key: string]: unknown; }",
169
169
  "default": null,
170
- "description": "Props for the label element. An explicit `htmlFor` key — even set to `undefined` — takes over the association. An `id` here is the one a group control's `aria-labelledby` points at; otherwise the label gets a generated one while the association is on. To point a group in an inner `Field` at this label by hand, pass that `id` with `htmlFor: undefined`, or the label keeps a generated `htmlFor` that nothing takes.",
170
+ "description": "Props for the label element. An explicit `htmlFor` key — even set to `undefined` — takes over the association. While the association is on, the label renders with the `id` given here, or a generated one, and a group control, a range `Slider`'s thumbs and an `Autocomplete`'s suggestion list point `aria-labelledby` at it. A label you take over gets no generated id, and no group points at it on its own. With an `id` here, though, a range `Slider` or an `Autocomplete` whose `id` your `htmlFor` names still points its thumbs or its suggestion list at it, in a `grouped` or `hasAddons` Field too. To point a group in an inner `Field` at this label by hand, pass that `id`. Nothing in the inner `Field` takes the generated `htmlFor`, so the label drops it after mounting, and adding `htmlFor: undefined` keeps it out of server-rendered markup too.",
171
171
  "required": false,
172
172
  "inherited": false,
173
173
  "deprecated": false
@@ -226,7 +226,7 @@
226
226
  },
227
227
  {
228
228
  "path": "Field.Label",
229
- "summary": "FieldLabel component for rendering a Bulma field label.",
229
+ "summary": "FieldLabel component for rendering a Bulma field label. It renders the label column of a horizontal `Field` (a `div` with the `field-label` class), not a `<label>`, so text placed straight in it names nothing. Put a `<label>` with the `label` class inside it and point its `htmlFor` at the control's `id`, or give the horizontal `Field` a `label` prop, which renders this column and its `<label>` for you. A `<label>` you put here names the control through its `htmlFor` alone: a range `Slider`'s thumbs and an `Autocomplete`'s suggestion list point `aria-labelledby` at a `Field`'s label only when its `label` prop renders it and it names the control. Where it would not name the control on its own, as in an inner `Field`, pass `labelProps={{ htmlFor, id }}` with a matching `id` on the control.",
230
230
  "component": null,
231
231
  "props": [
232
232
  {
@@ -91,7 +91,7 @@
91
91
  "name": "label",
92
92
  "type": "React.ReactNode",
93
93
  "default": null,
94
- "description": "Field label. Automatically associated with the file input via `htmlFor` — uses your `id` when provided, otherwise a generated one. The input then has two labels (this one plus the wrapping `file-label`); assistive tech reads both. Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`).",
94
+ "description": "Field label. Automatically associated with the file input via `htmlFor` — uses your `id` when provided, otherwise a generated one. The input then has two labels (this one plus the wrapping `file-label`); assistive tech reads both. Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`) and you set no `id` here.",
95
95
  "required": false,
96
96
  "inherited": false,
97
97
  "deprecated": false
@@ -99,15 +99,15 @@
99
99
  },
100
100
  {
101
101
  "title": "Horizontal — Validation Error",
102
- "code": "<Field horizontal label=\"Email\">\n <Field.Body>\n <Field>\n <Control iconLeftName=\"envelope\" iconRightName=\"exclamation-triangle\">\n <Input type=\"email\" color=\"danger\" value=\"hello@\" onChange={() => {}} />\n </Control>\n <p className=\"help is-danger\">This email is invalid</p>\n </Field>\n </Field.Body>\n</Field>"
102
+ "code": "<Field horizontal label=\"Email\" labelProps={{ htmlFor: 'invalid-email' }}>\n <Field.Body>\n <Field>\n <Control iconLeftName=\"envelope\" iconRightName=\"exclamation-triangle\">\n <Input\n id=\"invalid-email\"\n type=\"email\"\n color=\"danger\"\n value=\"hello@\"\n onChange={() => {}}\n />\n </Control>\n <p className=\"help is-danger\">This email is invalid</p>\n </Field>\n </Field.Body>\n</Field>"
103
103
  },
104
104
  {
105
105
  "title": "Horizontal — Addons (Phone with Country Code)",
106
- "code": "<Field horizontal label=\"Phone\">\n <Field.Body>\n <Field hasAddons>\n <Control>\n <Button isStatic>+44</Button>\n </Control>\n <Control isExpanded>\n <Input type=\"tel\" placeholder=\"Your phone number\" />\n </Control>\n </Field>\n </Field.Body>\n</Field>"
106
+ "code": "<Field horizontal label=\"Phone\" labelProps={{ htmlFor: 'phone-number' }}>\n <Field.Body>\n <Field hasAddons>\n <Control>\n <Button isStatic>+44</Button>\n </Control>\n <Control isExpanded>\n <Input id=\"phone-number\" type=\"tel\" placeholder=\"Your phone number\" />\n </Control>\n </Field>\n </Field.Body>\n</Field>"
107
107
  },
108
108
  {
109
109
  "title": "Horizontal — Submit Row",
110
- "code": "<>\n <Field horizontal label=\"Name\">\n <Field.Body>\n <Field>\n <Control>\n <Input placeholder=\"Your name\" />\n </Control>\n </Field>\n </Field.Body>\n </Field>\n <Field horizontal>\n <Field.Label />\n <Field.Body>\n <Field>\n <Control>\n <Button color=\"primary\">Submit</Button>\n </Control>\n </Field>\n </Field.Body>\n </Field>\n</>"
110
+ "code": "<>\n <Field horizontal label=\"Name\" labelProps={{ htmlFor: 'submit-row-name' }}>\n <Field.Body>\n <Field>\n <Control>\n <Input id=\"submit-row-name\" placeholder=\"Your name\" />\n </Control>\n </Field>\n </Field.Body>\n </Field>\n <Field horizontal>\n <Field.Label />\n <Field.Body>\n <Field>\n <Control>\n <Button color=\"primary\">Submit</Button>\n </Control>\n </Field>\n </Field.Body>\n </Field>\n</>"
111
111
  },
112
112
  {
113
113
  "title": "Disabled Fieldset",
@@ -119,14 +119,14 @@
119
119
  },
120
120
  {
121
121
  "title": "Context-Aware Rendering",
122
- "code": "function example() {\n return (\n <Field horizontal label=\"Username\">\n <Field.Body>\n <Field>\n <Input placeholder=\"Enter username\" />\n </Field>\n </Field.Body>\n </Field>\n );\n}"
122
+ "code": "function example() {\n return (\n <Field\n horizontal\n label=\"Username\"\n labelProps={{ htmlFor: 'username-field' }}\n >\n <Field.Body>\n <Field>\n <Input id=\"username-field\" placeholder=\"Enter username\" />\n </Field>\n </Field.Body>\n </Field>\n );\n}"
123
123
  },
124
124
  {
125
125
  "title": "Context-Aware Rendering",
126
- "code": "function example() {\n return (\n <Field horizontal label=\"Username\">\n <Field.Body>\n <Field>\n <Control iconLeftName=\"user\">\n <Input placeholder=\"Enter username\" />\n </Control>\n </Field>\n </Field.Body>\n </Field>\n );\n}"
126
+ "code": "function example() {\n return (\n <Field\n horizontal\n label=\"Username\"\n labelProps={{ htmlFor: 'username-field-control' }}\n >\n <Field.Body>\n <Field>\n <Control iconLeftName=\"user\">\n <Input id=\"username-field-control\" placeholder=\"Enter username\" />\n </Control>\n </Field>\n </Field.Body>\n </Field>\n );\n}"
127
127
  }
128
128
  ],
129
- "accessibility": "- Always provide a label. The `label` prop is automatically associated with the input (`htmlFor` plus a generated `id`, or your own `id` if you pass one), so clicking the label focuses the input and assistive technology announces it.\n- When composing with `Field` instead, the `Field`'s own `label` associates with the input automatically when the input sits directly in that labeled `Field` — a nested unlabeled `Field` (as in horizontal multi-field layouts) starts its own scope. Pass `labelProps={{ htmlFor }}` and a matching `id` for a stable id or to label across a nested `Field`.\n- Use the correct input `type` for semantics (`text`, `email`, etc.).",
129
+ "accessibility": "- Always provide a label. The `label` prop is automatically associated with the input (`htmlFor` plus a generated `id`, or your own `id` if you pass one), so clicking the label focuses the input and assistive technology announces it.\n- When composing with `Field` instead, the `Field`'s own `label` associates with the input automatically when the input sits directly in that labeled `Field` and has no `id` of its own. A nested unlabeled `Field` (as in horizontal multi-field layouts) starts its own scope. With an `id` of your own, or across a nested `Field`, the label names the input only when you pass that `id` as `labelProps={{ htmlFor }}`.\n- Use the correct input `type` for semantics (`text`, `email`, etc.).",
130
130
  "related": ["Control", "Field"],
131
131
  "storybook": "https://bestax.io/storybook/?path=/story/form-input--default",
132
132
  "summary": "The `Input` component provides a Bulma-styled text input, supporting colors, sizes, rounded corners, static state, hover/focus/loading states, and all Bulma helper props.",
@@ -143,7 +143,7 @@
143
143
  "name": "label",
144
144
  "type": "React.ReactNode",
145
145
  "default": null,
146
- "description": "Field label. Automatically associated with the input via `htmlFor` — uses your `id` when provided, otherwise a generated one. Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`).",
146
+ "description": "Field label. Automatically associated with the input via `htmlFor` — uses your `id` when provided, otherwise a generated one. Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`) and you set no `id` here.",
147
147
  "required": false,
148
148
  "inherited": false,
149
149
  "deprecated": false
@@ -89,7 +89,7 @@
89
89
  "name": "hasDelete",
90
90
  "type": "boolean",
91
91
  "default": "false",
92
- "description": "Shows a close (delete) button in the notification. It renders `type=\"button\"`, so it does not submit a form around it.",
92
+ "description": "Shows a close (delete) button in the notification. It renders `type=\"button\"`, so it does not submit a form around it. The notification also takes `has-delete`, which pads its end by `--bulma-notification-delete-padding-inline-end` so the text clears the button. That rule ships in bestax's stylesheets, not Bulma's, so an app styled by Bulma's CSS alone keeps Bulma's padding.",
93
93
  "required": false,
94
94
  "inherited": false,
95
95
  "deprecated": false
@@ -119,6 +119,12 @@
119
119
  }
120
120
  ],
121
121
  "cssVars": [
122
+ {
123
+ "css": "--bulma-notification-delete-padding-inline-end",
124
+ "sass": "$notification-delete-padding-inline-end",
125
+ "default": "calc(1rem + 1.25rem + 0.75rem)",
126
+ "scope": "root"
127
+ },
122
128
  {
123
129
  "css": "--bulma-notification-h",
124
130
  "sass": null,
@@ -71,7 +71,7 @@
71
71
  "name": "label",
72
72
  "type": "React.ReactNode",
73
73
  "default": null,
74
- "description": "Field label. Automatically associated with the number input via `htmlFor` — uses your `id` when provided, otherwise a generated one. Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`). Not rendered either by a plusminus `bare` Numberinput outside a `Field`.",
74
+ "description": "Field label. Automatically associated with the number input via `htmlFor` — uses your `id` when provided, otherwise a generated one. Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`) and you set no `id` here. Not rendered either by a plusminus `bare` Numberinput outside a `Field`.",
75
75
  "required": false,
76
76
  "inherited": false,
77
77
  "deprecated": false
@@ -55,7 +55,7 @@
55
55
  },
56
56
  {
57
57
  "title": "Horizontal — Select in Narrow Field",
58
- "code": "<Field horizontal label=\"Department\">\n <Field.Body>\n <Field narrow>\n <Control>\n <Select isFullwidth>\n <option>Business development</option>\n <option>Marketing</option>\n <option>Sales</option>\n </Select>\n </Control>\n </Field>\n </Field.Body>\n</Field>"
58
+ "code": "<Field\n horizontal\n label=\"Department\"\n labelProps={{ htmlFor: 'department-field' }}\n>\n <Field.Body>\n <Field narrow>\n <Control>\n <Select id=\"department-field\" isFullwidth>\n <option>Business development</option>\n <option>Marketing</option>\n <option>Sales</option>\n </Select>\n </Control>\n </Field>\n </Field.Body>\n</Field>"
59
59
  },
60
60
  {
61
61
  "title": "Context-Aware Rendering",
@@ -63,14 +63,14 @@
63
63
  },
64
64
  {
65
65
  "title": "Context-Aware Rendering",
66
- "code": "function example() {\n return (\n <Field horizontal label=\"Country\">\n <Field.Body>\n <Field>\n <Select>\n <option value=\"\">Please select</option>\n <option value=\"us\">United States</option>\n <option value=\"uk\">United Kingdom</option>\n <option value=\"ca\">Canada</option>\n </Select>\n </Field>\n </Field.Body>\n </Field>\n );\n}"
66
+ "code": "function example() {\n return (\n <Field horizontal label=\"Country\" labelProps={{ htmlFor: 'country-field' }}>\n <Field.Body>\n <Field>\n <Select id=\"country-field\">\n <option value=\"\">Please select</option>\n <option value=\"us\">United States</option>\n <option value=\"uk\">United Kingdom</option>\n <option value=\"ca\">Canada</option>\n </Select>\n </Field>\n </Field.Body>\n </Field>\n );\n}"
67
67
  },
68
68
  {
69
69
  "title": "Context-Aware Rendering",
70
- "code": "function example() {\n return (\n <Field horizontal label=\"Country\">\n <Field.Body>\n <Field>\n <Control iconLeftName=\"globe\">\n <Select>\n <option value=\"\">Please select</option>\n <option value=\"us\">United States</option>\n <option value=\"uk\">United Kingdom</option>\n <option value=\"ca\">Canada</option>\n </Select>\n </Control>\n </Field>\n </Field.Body>\n </Field>\n );\n}"
70
+ "code": "function example() {\n return (\n <Field\n horizontal\n label=\"Country\"\n labelProps={{ htmlFor: 'country-field-control' }}\n >\n <Field.Body>\n <Field>\n <Control iconLeftName=\"globe\">\n <Select id=\"country-field-control\">\n <option value=\"\">Please select</option>\n <option value=\"us\">United States</option>\n <option value=\"uk\">United Kingdom</option>\n <option value=\"ca\">Canada</option>\n </Select>\n </Control>\n </Field>\n </Field.Body>\n </Field>\n );\n}"
71
71
  }
72
72
  ],
73
- "accessibility": "- Always provide a label. Select's `label` prop is automatically associated with the `<select>` (`htmlFor` plus a generated `id`, or your own `id` if you pass one).\n- When composing with `Field` instead, the `Field`'s own `label` associates with the select automatically when the select sits directly in that labeled `Field` — a nested unlabeled `Field` (as in horizontal multi-field layouts) starts its own scope. Pass `labelProps={{ htmlFor }}` and a matching `id` for a stable id or to label across a nested `Field`.\n- Use the `multiple` and `multipleSize` props for multi-select dropdowns.",
73
+ "accessibility": "- Always provide a label. Select's `label` prop is automatically associated with the `<select>` (`htmlFor` plus a generated `id`, or your own `id` if you pass one).\n- When composing with `Field` instead, the `Field`'s own `label` associates with the select automatically when the select sits directly in that labeled `Field` and has no `id` of its own. A nested unlabeled `Field` (as in horizontal multi-field layouts) starts its own scope. With an `id` of your own, or across a nested `Field`, the label names the select only when you pass that `id` as `labelProps={{ htmlFor }}`.\n- Use the `multiple` and `multipleSize` props for multi-select dropdowns.",
74
74
  "related": ["Control", "Field"],
75
75
  "storybook": "https://bestax.io/storybook/?path=/story/form-select--default",
76
76
  "summary": "The `Select` component provides a Bulma-styled dropdown for selecting one or more options.",
@@ -87,7 +87,7 @@
87
87
  "name": "label",
88
88
  "type": "React.ReactNode",
89
89
  "default": null,
90
- "description": "Field label. Automatically associated with the select via `htmlFor` — uses your `id` when provided, otherwise a generated one. Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`).",
90
+ "description": "Field label. Automatically associated with the select via `htmlFor` — uses your `id` when provided, otherwise a generated one. Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`) and you set no `id` here.",
91
91
  "required": false,
92
92
  "inherited": false,
93
93
  "deprecated": false
@@ -29,6 +29,14 @@
29
29
  "title": "Custom Range",
30
30
  "code": "function example() {\n const [value, setValue] = useState(500);\n return (\n <Block>\n <Slider\n value={value}\n onChange={setValue}\n min={0}\n max={1000}\n step={50}\n showOutput\n color=\"primary\"\n />\n <Paragraph mt=\"4\">Value: ${value}</Paragraph>\n </Block>\n );\n}"
31
31
  },
32
+ {
33
+ "title": "Range Slider",
34
+ "code": "function example() {\n const [range, setRange] = useState<[number, number]>([200, 800]);\n return (\n <Block>\n <Slider\n range\n label=\"Price range\"\n value={range}\n onChange={setRange}\n min={0}\n max={1000}\n step={50}\n color=\"primary\"\n />\n <Paragraph mt=\"4\">\n ${range[0]} to ${range[1]}\n </Paragraph>\n </Block>\n );\n}"
35
+ },
36
+ {
37
+ "title": "Range Slider",
38
+ "code": "<Slider\n range\n label=\"Price range\"\n ariaLabel={['Lowest price', 'Highest price']}\n defaultValue={[200, 800]}\n max={1000}\n/>"
39
+ },
32
40
  {
33
41
  "title": "Custom Output Format",
34
42
  "code": "function example() {\n const [value, setValue] = useState(50);\n return (\n <Block>\n <Slider\n value={value}\n onChange={setValue}\n showOutput\n color=\"success\"\n formatOutput={v => `${v}%`}\n />\n <Paragraph mt=\"4\">Progress: {value}%</Paragraph>\n </Block>\n );\n}"
@@ -50,7 +58,7 @@
50
58
  "code": "function example() {\n return (\n <Field\n horizontal\n label=\"Volume\"\n labelProps={{ htmlFor: 'volume-field-control' }}\n >\n <Field.Body>\n <Field>\n <Control iconLeftName=\"volume-up\">\n <Slider\n id=\"volume-field-control\"\n defaultValue={50}\n color=\"primary\"\n />\n </Control>\n </Field>\n </Field.Body>\n </Field>\n );\n}"
51
59
  }
52
60
  ],
53
- "accessibility": "- Uses native `<input type=\"range\">` element\n- The `label` prop, or the label of a `Field` the slider sits in, names its input. In range mode the label targets the low thumb, but each thumb keeps its own `aria-label` (`ariaLabel`, or \"Minimum value\" and \"Maximum value\"), which wins.\n- Has `aria-valuenow`, `aria-valuemin`, and `aria-valuemax` attributes\n- Fully keyboard accessible with arrow keys\n- Focus states clearly visible",
61
+ "accessibility": "- Uses native `<input type=\"range\">` element\n- The `label` prop, or the label of a `Field` the slider sits in, names its input.\n- In range mode the label starts each thumb's name, so `<Slider range label=\"Price range\" />` announces \"Price range Minimum value\" and \"Price range Maximum value\". Each thumb points `aria-labelledby` at the label and at a hidden span holding its own text, because an `aria-label` would outrank the label. The label's `for` still lands on the low thumb, so clicking the label focuses it.\n- `ariaLabel` wins over the label. In range mode each entry is that thumb's whole name, with no label in front, and an empty entry counts as unset.\n- An `aria-label` or `aria-labelledby` you pass straight to a range Slider names the whole Slider, so it takes the label's place at the start of both thumbs' names: `<Slider range aria-label=\"Budget\" />` announces \"Budget Minimum value\" and \"Budget Maximum value\". An `ariaLabel` entry still wins for its own thumb. To keep a label out of the thumbs' names altogether, pass `aria-labelledby={undefined}`, and they stay \"Minimum value\" and \"Maximum value\".\n- A `Field` label you wire by hand with `labelProps={{ htmlFor }}`, as in a horizontal form, gets no generated id, so give it one of your own there as well, as in `labelProps={{ htmlFor: 'price', id: 'price-label' }}`, and it starts both thumbs' names the same way. Without that `id` it reaches a range Slider only through its `for`, which the thumbs' default `aria-label` outranks, so they stay \"Minimum value\" and \"Maximum value\".\n- Has `aria-valuenow`, `aria-valuemin`, and `aria-valuemax` attributes\n- Fully keyboard accessible with arrow keys\n- Focus states clearly visible",
54
62
  "related": ["Numberinput", "Progress"],
55
63
  "storybook": "https://bestax.io/storybook/?path=/story/form-slider",
56
64
  "summary": "The `Slider` component provides a range slider input for selecting values within a range.",
@@ -112,7 +120,7 @@
112
120
  "name": "ariaLabel",
113
121
  "type": "string | [string, string]",
114
122
  "default": null,
115
- "description": "ARIA label(s) for the slider thumb(s).",
123
+ "description": "ARIA label(s) for the slider thumb(s), which win over a label, the Slider's own or a surrounding `Field`'s. In range mode each entry is that thumb's whole name, and a thumb without one is named \"Minimum value\" or \"Maximum value\", after whatever names the Slider: a label, or in its place an `aria-label` or `aria-labelledby` given to the Slider, as in \"Budget Minimum value\" and \"Budget Maximum value\". An `aria-labelledby` set to `undefined` keeps every label out of both names.",
116
124
  "required": false,
117
125
  "inherited": true,
118
126
  "deprecated": false
@@ -139,7 +147,7 @@
139
147
  "name": "label",
140
148
  "type": "React.ReactNode",
141
149
  "default": null,
142
- "description": "Field label. Automatically associated with the slider input via `htmlFor` — uses your `id` when provided, otherwise a generated one. In range mode the label targets the low (minimum) thumb. Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`).",
150
+ "description": "Field label. Automatically associated with the slider input via `htmlFor` — uses your `id` when provided, otherwise a generated one. In range mode the `htmlFor` targets the low (minimum) thumb, and the label also starts each thumb's name through `aria-labelledby`, as in \"Price Minimum value\" and \"Price Maximum value\", unless an `aria-label` or `aria-labelledby` on the Slider takes its place there. Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`) and you set no `id` here, naming a range Slider's thumbs the same way. A `Field` label you wire by hand to the low thumb with `labelProps.htmlFor` names the thumbs too when you also give it an `id` there, since the `Field` generates none for it.",
143
151
  "required": false,
144
152
  "inherited": true,
145
153
  "deprecated": false
@@ -148,7 +156,7 @@
148
156
  "name": "labelProps",
149
157
  "type": "React.LabelHTMLAttributes<HTMLLabelElement> & { [key: string]: unknown; }",
150
158
  "default": null,
151
- "description": "Props for the label element. An explicit `htmlFor` here overrides the automatic association (no id is generated then).",
159
+ "description": "Props for the label element. An explicit `htmlFor` here overrides the automatic association (no id is generated then). In range mode an `id` here is the one the thumbs' `aria-labelledby` points at, in place of a generated one.",
152
160
  "required": false,
153
161
  "inherited": true,
154
162
  "deprecated": false
@@ -75,7 +75,7 @@
75
75
  "name": "label",
76
76
  "type": "React.ReactNode",
77
77
  "default": null,
78
- "description": "Field label. Automatically associated with the text input via `htmlFor` — uses your `id` when provided, otherwise a generated one. Not wired when the tag limit is reached (the input is not rendered). Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`); it then replaces the `aria-label=\"Add tag\"` fallback too.",
78
+ "description": "Field label. Automatically associated with the text input via `htmlFor` — uses your `id` when provided, otherwise a generated one. Not wired when the tag limit is reached (the input is not rendered). Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`) and you set no `id` here; it then replaces the `aria-label=\"Add tag\"` fallback too.",
79
79
  "required": false,
80
80
  "inherited": false,
81
81
  "deprecated": false
@@ -47,14 +47,14 @@
47
47
  },
48
48
  {
49
49
  "title": "Context-Aware Rendering",
50
- "code": "function example() {\n return (\n <Field horizontal label=\"Message\">\n <Field.Body>\n <Field>\n <TextArea placeholder=\"Enter your message\" />\n </Field>\n </Field.Body>\n </Field>\n );\n}"
50
+ "code": "function example() {\n return (\n <Field horizontal label=\"Message\" labelProps={{ htmlFor: 'message-field' }}>\n <Field.Body>\n <Field>\n <TextArea id=\"message-field\" placeholder=\"Enter your message\" />\n </Field>\n </Field.Body>\n </Field>\n );\n}"
51
51
  },
52
52
  {
53
53
  "title": "Context-Aware Rendering",
54
- "code": "function example() {\n return (\n <Field horizontal label=\"Message\">\n <Field.Body>\n <Field>\n <Control iconLeftName=\"comment\">\n <TextArea placeholder=\"Enter your message\" />\n </Control>\n </Field>\n </Field.Body>\n </Field>\n );\n}"
54
+ "code": "function example() {\n return (\n <Field\n horizontal\n label=\"Message\"\n labelProps={{ htmlFor: 'message-field-control' }}\n >\n <Field.Body>\n <Field>\n <Control iconLeftName=\"comment\">\n <TextArea\n id=\"message-field-control\"\n placeholder=\"Enter your message\"\n />\n </Control>\n </Field>\n </Field.Body>\n </Field>\n );\n}"
55
55
  }
56
56
  ],
57
- "accessibility": "- Always provide a label. TextArea's `label` prop is automatically associated with the `<textarea>` (`htmlFor` plus a generated `id`, or your own `id` if you pass one).\n- When composing with `Field` instead, the `Field`'s own `label` associates with the textarea automatically when the textarea sits directly in that labeled `Field` — a nested unlabeled `Field` (as in horizontal multi-field layouts) starts its own scope. Pass `labelProps={{ htmlFor }}` and a matching `id` for a stable id or to label across a nested `Field`.\n- Use the `rows` prop to set an appropriate height for your content.",
57
+ "accessibility": "- Always provide a label. TextArea's `label` prop is automatically associated with the `<textarea>` (`htmlFor` plus a generated `id`, or your own `id` if you pass one).\n- When composing with `Field` instead, the `Field`'s own `label` associates with the textarea automatically when the textarea sits directly in that labeled `Field` and has no `id` of its own. A nested unlabeled `Field` (as in horizontal multi-field layouts) starts its own scope. With an `id` of your own, or across a nested `Field`, the label names the textarea only when you pass that `id` as `labelProps={{ htmlFor }}`.\n- Use the `rows` prop to set an appropriate height for your content.",
58
58
  "related": ["Control", "Field"],
59
59
  "storybook": "https://bestax.io/storybook/?path=/story/form-textarea--default",
60
60
  "summary": "The `TextArea` component provides a Bulma-styled multi-line text input, supporting color, size, hover/focus/loading states, fixed size, and all Bulma helper props.",
@@ -71,7 +71,7 @@
71
71
  "name": "label",
72
72
  "type": "React.ReactNode",
73
73
  "default": null,
74
- "description": "Field label. Automatically associated with the textarea via `htmlFor` — uses your `id` when provided, otherwise a generated one. Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`).",
74
+ "description": "Field label. Automatically associated with the textarea via `htmlFor` — uses your `id` when provided, otherwise a generated one. Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`) and you set no `id` here.",
75
75
  "required": false,
76
76
  "inherited": false,
77
77
  "deprecated": false
@@ -107,7 +107,7 @@
107
107
  "name": "bulmaVars",
108
108
  "type": "BulmaVars",
109
109
  "default": "{}",
110
- "description": "Bulma CSS variables to set, by name, such as `{ '--bulma-primary-h': '210' }`. Only Bulma's own variables, the keys the `BulmaVars` type allows, are applied, and an empty value is skipped. Where a variable prop, such as `primaryH`, sets the same variable, the prop wins.",
110
+ "description": "Bulma CSS variables to set, by name, such as `{ '--bulma-primary-h': '210' }`. Only Bulma's own variables, the keys the `BulmaVars` type allows, are applied, and an empty value is skipped. Where a variable prop, such as `primaryH`, sets the same variable, the prop wins. Theme writes them on its wrapper, or at `:root` under `isRoot`, so only a variable that inherits from there changes anything. Bulma declares many component variables, such as `--bulma-card-radius` or `--bulma-delete-dimensions`, on the component's own element, and that declaration beats any value set further up. Those keys are accepted but do nothing here, and warn in development. Set them on the component itself, with `className` or `style`, or through Sass. Each component's API page says where Bulma declares its variables. Some global variables Bulma also declares again on one component, which keeps its own value while everything else takes the Theme's. Those do not warn, because the value still reaches the rest of the page.",
111
111
  "required": false,
112
112
  "inherited": false,
113
113
  "deprecated": false
@@ -467,5 +467,5 @@
467
467
  }
468
468
  ],
469
469
  "cssVars": [],
470
- "doc": "# Theme\n\n## Overview\n\nThe `Theme` component provides a powerful way to customize Bulma's appearance using CSS custom properties (CSS variables). It allows you to override Bulma's design tokens like colors, spacing, typography, and other visual properties either globally or locally within specific component trees. The Theme component supports both CSS variable injection via props and direct CSS variable objects.\n\n---\n\n## Import\n\n```tsx\nimport { Theme } from '@allxsmith/bestax-bulma';\n```\n\n---\n\n## Usage\n\n### Basic Theme Customization\n\n```tsx\nfunction BasicThemeCustomization() {\n return (\n <Theme primaryH=\"270\" primaryS=\"100%\" primaryL=\"50%\">\n <Box p=\"4\">\n <Button color=\"primary\">Purple Primary Button</Button>\n </Box>\n </Theme>\n );\n}\n```\n\n### Global Theme (Root Level)\n\n```tsx\nfunction GlobalTheme() {\n return (\n <Theme isRoot={true} primaryH=\"270\" primaryS=\"100%\" primaryL=\"50%\">\n <div>\n <Button color=\"primary\">Global Purple Theme</Button>\n <Button color=\"info\">Info Button</Button>\n </div>\n </Theme>\n );\n}\n```\n\n### Dark Mode\n\nThe `colorMode` prop sets Bulma's `data-theme` on `<html>`, flipping the light/dark scheme globally.\nUse `'system'` to follow the OS preference.\n\nA prefixed stylesheet reads Bulma's prefixed attribute instead (`data-bestax-theme` for\n`bestax-prefixed.css` and `bestax-no-helpers-prefixed.css`). Under a `ConfigProvider`\n`classPrefix`, `colorMode` writes that attribute as well as `data-theme`, so it works with the\nprefixed builds.\n\n:::warning Single-mode designs should pin `colorMode`\n\nOmitting `colorMode` preserves whatever `data-theme` is already set — and when nothing has set\none (the usual case), the scheme follows the visitor's OS: Bulma's text colors flip on a\ndark-mode machine even if your design is light-only, breaking contrast against any fixed custom\nbackgrounds. If you support only one mode, pin it (`<Theme isRoot colorMode=\"light\">`); if you\nsupport both, don't hardcode surface/text colors — see\n[Dark Mode & Contrast](https://bestax.io/docs/guides/features/css-variables#dark-mode--contrast).\n\n:::\n\n```tsx\nimport { useState } from 'react';\n\nfunction App() {\n const [mode, setMode] = useState<'light' | 'dark' | 'system'>('system');\n return (\n <Theme isRoot colorMode={mode}>\n <Button color=\"primary\" onClick={() => setMode('dark')}>\n Dark\n </Button>\n <Button color=\"primary\" onClick={() => setMode('light')}>\n Light\n </Button>\n <Button onClick={() => setMode('system')}>System</Button>\n </Theme>\n );\n}\n```\n\n### Local Theme (Component Level)\n\n```tsx\nfunction LocalTheme() {\n return (\n <div>\n <Button color=\"primary\">Standard Primary</Button>\n\n <Theme primaryH=\"120\" primaryS=\"100%\" primaryL=\"40%\">\n <Button color=\"primary\">Green Primary</Button>\n </Theme>\n\n <Button color=\"primary\">Standard Primary Again</Button>\n </div>\n );\n}\n```\n\n### Using CSS Variables Object\n\n```tsx\nfunction SunsetTheme() {\n const sunsetTheme = {\n '--bulma-scheme-h': '18',\n '--bulma-scheme-s': '90%',\n '--bulma-light-l': '85%',\n '--bulma-dark-l': '20%',\n '--bulma-primary-h': '25',\n '--bulma-primary-s': '85%',\n '--bulma-primary-l': '55%',\n };\n\n return (\n <Theme bulmaVars={sunsetTheme} isRoot>\n <Box p=\"4\">\n <Title>Sunset Theme</Title>\n <Button color=\"primary\">Sunset Button</Button>\n <Button color=\"info\">Info Button</Button>\n </Box>\n </Theme>\n );\n}\n```\n\n### Advanced CSS Variables Usage\n\nBulma derives the body font and the control radius from these variables at `:root`, so set\ntypography and radius on an `isRoot` theme.\n\n```tsx\nfunction TypographyTheme() {\n const typographyTheme = {\n '--bulma-family-primary': '\"Helvetica Neue\", sans-serif',\n '--bulma-family-code': '\"Fira Code\", monospace',\n '--bulma-size-normal': '16px',\n '--bulma-weight-bold': '700',\n };\n\n return (\n <Theme isRoot bulmaVars={typographyTheme}>\n <Title>Custom Typography</Title>\n <SubTitle>With custom fonts and weights</SubTitle>\n </Theme>\n );\n}\n```\n\n```tsx\nfunction RadiusTheme() {\n // `radius` is the border radius helper, as on every component, so the radius scale goes through bulmaVars\n const radii = {\n '--bulma-radius-small': '6px',\n '--bulma-radius': '12px',\n '--bulma-radius-medium': '16px',\n '--bulma-radius-large': '20px',\n };\n\n return (\n <Theme isRoot bulmaVars={radii}>\n <Button color=\"primary\">Rounder Button</Button>\n </Theme>\n );\n}\n```\n\nSpacing that Bulma reads where it is used, such as the block and column gaps, works on a scoped\ntheme:\n\n```tsx\nfunction SpacingTheme() {\n const spacingTheme = {\n '--bulma-block-spacing': '2rem',\n '--bulma-column-gap': '1rem',\n };\n\n return (\n <Theme bulmaVars={spacingTheme}>\n <Columns>\n <Column>\n <Box>Wider column gap</Box>\n <Box>Wider block spacing</Box>\n </Column>\n <Column>\n <Box>Wider column gap</Box>\n </Column>\n </Columns>\n </Theme>\n );\n}\n```\n\n### Complete Color Scheme\n\n```tsx\nfunction ForestTheme() {\n const forestScheme = {\n schemeH: '150', // forest green hue\n schemeS: '50%',\n lightL: '80%',\n lightInvertL: '20%',\n darkL: '18%',\n darkInvertL: '85%',\n softL: '55%',\n boldL: '35%',\n primaryH: '160',\n primaryS: '60%',\n primaryL: '45%',\n linkH: '155',\n linkS: '65%',\n linkL: '40%',\n successH: '120',\n successS: '70%',\n successL: '45%',\n warningH: '45',\n warningS: '85%',\n warningL: '55%',\n dangerH: '355',\n dangerS: '75%',\n dangerL: '50%',\n hoverBackgroundLDelta: '4%',\n activeBackgroundLDelta: '8%',\n };\n\n return (\n <Theme {...forestScheme} isRoot>\n <Box p=\"4\">\n <Title>Forest Theme</Title>\n <Button color=\"primary\">Forest Primary</Button>\n <Button color=\"success\">Success</Button>\n <Button color=\"info\">Info</Button>\n <Button color=\"warning\">Warning</Button>\n <Button color=\"danger\">Danger</Button>\n </Box>\n </Theme>\n );\n}\n```\n\n### Theme with Styling\n\n```tsx\nfunction StyledTheme() {\n return (\n <Theme\n className=\"custom-theme-wrapper\"\n primaryH=\"45\"\n primaryS=\"100%\"\n primaryL=\"50%\"\n p=\"5\"\n m=\"3\"\n textAlign=\"centered\"\n >\n <Title>Styled Theme Container</Title>\n <Button color=\"primary\">Styled Button</Button>\n </Theme>\n );\n}\n```\n\n### Nested Themes\n\n```tsx\nfunction NestedThemes() {\n return (\n <Theme primaryH=\"210\" primaryS=\"60%\" primaryL=\"45%\" p=\"4\">\n <Title>Outer Theme (Blue)</Title>\n <Button color=\"primary\">Blue Primary</Button>\n\n <Theme primaryH=\"120\" primaryS=\"70%\" primaryL=\"40%\" p=\"3\" mt=\"4\">\n <Title size=\"4\">Inner Theme (Green)</Title>\n <Button color=\"primary\">Green Primary</Button>\n </Theme>\n\n <Button color=\"primary\" mt=\"3\">\n Blue Primary Again\n </Button>\n </Theme>\n );\n}\n```\n\n### Theme with ConfigProvider\n\n```tsx\nfunction PrefixedTheme() {\n return (\n <ConfigProvider classPrefix=\"bestax-\">\n <Theme primaryH=\"300\" primaryS=\"80%\" primaryL=\"50%\" isRoot>\n <Button color=\"primary\">Prefixed & Themed Button</Button>\n </Theme>\n </ConfigProvider>\n );\n}\n```\n\n---\n\n## Best Practices\n\n### Global vs Local Themes\n\n- Use `isRoot={true}` for application-wide themes that should affect all components\n- Use local themes (default `isRoot={false}`) for component-specific styling or theme variations\n- Local themes inherit from parent themes and can override specific variables\n- Several `isRoot` themes can be mounted at once, such as a color theme at the app root and a\n typography theme next to it. Each adds its own variables at `:root`; where two set the same\n variable, the inner or later-mounted one wins, as with nested local themes, and unmounting one\n removes only its own variables\n\n### CSS Variable Naming\n\n- All Bulma CSS variables follow the `--bulma-*` naming convention\n- Named props exist for the scheme and color variables only (like `primaryH`); every other variable, typography and radius included, goes through `bulmaVars`\n- `bulmaVars` keys are the full variable names (`'--bulma-family-primary'`), not camelCase\n- `bulmaVars` also accepts the scheme surface variables — `--bulma-scheme-main`, `--bulma-scheme-main-bis`, `--bulma-scheme-main-ter`, `--bulma-scheme-invert`, `--bulma-scheme-invert-bis`, `--bulma-scheme-invert-ter` — so overriding `--bulma-scheme-main-bis`/`-ter` re-tints every scheme-background band (e.g., `<Section bgColor=\"scheme-main-bis\">`) at once\n- `bulmaVars` also accepts `--bulma-shadow` (plus `--bulma-shadow-h`/`-s`/`-l`) — the upstream token that `.box`, `.card`, `.dropdown`, and `.panel` derive their own shadow variables from. Those selectors re-declare their derived variable (e.g. `--bulma-box-shadow`) on themselves, so setting it from an ancestor `Theme` is only inherited and never wins; overriding `--bulma-shadow` itself is the one override that cascades. There is no individual `shadow` prop for this — `shadow` already names the unrelated `shadowless` helper class — so reach it through `bulmaVars`\n\n### Performance Considerations\n\n- Prefer setting themes at higher levels in your component tree rather than deeply nested\n- CSS variables are inherited, so child themes only need to override specific variables\n\n### Color System\n\n- Bulma uses HSL (Hue, Saturation, Lightness) for its color system\n- Hue values range from 0-360 (color wheel degrees)\n- Saturation and Lightness are typically percentages (e.g., '50%')\n- The scheme variables control the base color relationships\n\n---\n\n## API Reference\n\n### Named Props and `bulmaVars`\n\n`ThemeProps` has a named, camelCase prop for each scheme and color variable, listed under\n[CSS Variable Props](https://bestax.io/docs/api/helpers/theme#css-variable-props). Each converts to its CSS variable by the same rule:\n\n```\npropName → --bulma-prop-name\n```\n\n- `primaryH` → `--bulma-primary-h`\n- `schemeS` → `--bulma-scheme-s`\n- `lightL` → `--bulma-light-l`\n- `hoverBackgroundLDelta` → `--bulma-hover-background-l-delta`\n\nEvery other variable (typography, radius, spacing, and the rest) has no named prop in\n`ThemeProps` and goes through `bulmaVars`, keyed by its full `--bulma-*` name. JavaScript code\nthat passes one as a camelCase prop (`familyPrimary`) may still render, but it is outside the\ntyped API, so move it to `bulmaVars`:\n\n```tsx\n<Theme\n isRoot\n primaryH=\"350\"\n primaryS=\"73%\"\n primaryL=\"44%\"\n bulmaVars={{\n '--bulma-radius': '2px',\n '--bulma-radius-small': '1px',\n '--bulma-family-primary': '\"IBM Plex Sans\", system-ui, sans-serif',\n '--bulma-family-code': '\"IBM Plex Mono\", ui-monospace, monospace',\n }}\n>\n <App />\n</Theme>\n```\n\nSome of those names are already helper props, so they cannot double as CSS-variable props:\n\n- `radius` is the border radius helper, as on every other component, so `<Theme radius=\"radiusless\">`\n adds `is-radiusless` to the wrapper. On a Theme it also sets `--bulma-radius` to `0`, so what\n is inside the Theme loses its radius too; under `isRoot` it writes that at `:root`, squaring\n everything on the page that takes its radius from it. The sizes (`small`, `normal`, `large`,\n `rounded`) add their `has-radius-*` class to the wrapper and set no variable, so they round\n the wrapper and leave what is inside alone; under `isRoot` there is no wrapper, so they do\n nothing and warn in development. Set any other `--bulma-radius` through\n `bulmaVars`. `<Theme radius=\"2px\" />` is a type error; in JavaScript it still sets\n `--bulma-radius`, which is how the prop used to behave, but that route is deprecated and logs\n a warning in development.\n- `shadow` is the `shadowless` helper. Set `--bulma-shadow` through `bulmaVars`.\n- `columnGap` is the column gap helper, as on every other component, so `<Theme columnGap=\"2\">`\n adds `is-column-gap-2` to the wrapper and sets no variable; under `isRoot` there is no wrapper,\n so it does nothing and warns in development. Set the columns gutter, `--bulma-column-gap`,\n through `bulmaVars`. Before the gap helpers, JavaScript could pass `columnGap` to set that\n variable. A string that is not a gap step (`columnGap=\"1rem\"`) still does, through a\n deprecated route that logs a warning in development, but a gap step does not:\n `columnGap=\"0\"`, which used to zero the gutters inside the Theme, now adds `is-column-gap-0`\n to the wrapper.\n\n### Props vs bulmaVars\n\nYou can set the scheme and color variables in two ways:\n\n1. **Named Props** (recommended where one exists):\n\n ```tsx\n <Theme primaryH=\"270\" primaryS=\"100%\" primaryL=\"50%\">\n <App />\n </Theme>\n ```\n\n2. **bulmaVars Object** (for every other variable, or when you have many to set):\n\n ```tsx\n <Theme\n bulmaVars={{\n '--bulma-primary-h': '270',\n '--bulma-primary-s': '100%',\n '--bulma-primary-l': '50%',\n '--bulma-block-spacing': '2rem',\n }}\n >\n <App />\n </Theme>\n ```\n\n3. **Combined** (props take precedence over bulmaVars):\n ```tsx\n <Theme\n primaryH=\"270\" // This takes precedence\n bulmaVars={{\n '--bulma-primary-h': '180', // This is overridden\n '--bulma-block-spacing': '2rem',\n }}\n >\n <App />\n </Theme>\n ```\n\n### TypeScript Support\n\n`bulmaVars` is typed as a partial record keyed by the union of every variable `Theme` can set.\nWrite it as an object literal and your editor autocompletes the keys and flags a misspelled or\nunsupported one as a type error. A separately declared object only has to share one valid key\nto typecheck, so a typo in it is silently dropped; annotate it with\n`satisfies ThemeProps['bulmaVars']` to keep the check:\n\n```tsx\n<Theme\n primaryH=\"270\"\n bulmaVars={{\n '--bulma-block-spacing': '2rem', // start typing '--bulma-' to list the rest\n }}\n>\n <App />\n</Theme>\n```\n\n---\n\n## See Also\n\n- [ConfigProvider](https://bestax.io/docs/api/helpers/config) - For class prefixing and configuration\n- [useBulmaClasses](https://bestax.io/docs/api/helpers/usebulmaclasses) - For applying Bulma helper classes\n- [Bulma CSS Variables Documentation](https://bulma.io/documentation/features/css-variables/) - Official Bulma CSS variables reference\n\n---\n\n## Props\n\n| Prop | Type | Description |\n| ----------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `children` | `ReactNode` | The child components to apply the theme to. |\n| `className` | `string` | Additional CSS classes for the theme wrapper. A root theme has no wrapper, so under `isRoot` it does nothing and warns in development. |\n| `isRoot` | `boolean` | When `true`, applies CSS variables globally at `:root` level. When `false` (default), applies variables only to the wrapper div. Several root themes compose; an inner or later-mounted one wins a variable they share. A root theme renders no wrapper, so `className` and the helper props, such as `m` or `shadow`, do nothing on it and warn in development. |\n| `colorMode` | `'light' \\| 'dark' \\| 'system'` | Sets Bulma's light/dark scheme by writing the `data-theme` attribute on `<html>`, plus `data-<prefix>theme` inside a `ConfigProvider` with a `classPrefix`. If the prefix cannot form an attribute name, `'light'` and `'dark'` write only `data-theme` and warn in development. Always global (even on a scoped `Theme`). `'system'` removes them so Bulma follows the OS `prefers-color-scheme`. Omit to leave the current setting untouched. |\n| `bulmaVars` | `ThemeProps['bulmaVars']` | Object mapping Bulma CSS variable names to string values (e.g., `{'--bulma-primary-h': '210'}`). Keys are limited to the variables listed below; anything else is not applied. |\n| `columnGap` | [`BulmaGapStep`](https://bestax.io/docs/api/helpers/valid-values) | The column gap helper: a gap step adds `is-column-gap-<step>` to the wrapper div and sets no variable, so under `isRoot` it does nothing and warns in development. Set `--bulma-column-gap` through `bulmaVars`. Any other string still sets `--bulma-column-gap`, a deprecated route that warns in development. |\n| `radius` | `'radiusless' \\| 'small' \\| 'normal' \\| 'large' \\| 'rounded'` | The border radius helper. `radiusless` adds `is-radiusless` to the wrapper div and sets `--bulma-radius` to `0`, at `:root` under `isRoot`. The sizes add their `has-radius-*` class to the wrapper and set no variable, so under `isRoot` they do nothing and warn in development. Any other string still sets `--bulma-radius`, a deprecated route that warns in development. |\n\n### CSS Variable Props\n\nThe Theme component accepts individual CSS variable props that correspond to Bulma's design tokens. These props are automatically converted to their corresponding CSS custom properties (e.g., `primaryH` → `--bulma-primary-h`).\n\n#### Scheme Variables\n\n| Prop | Type | Description | CSS Variable |\n| ------------------------ | -------- | ------------------------------------------------- | ----------------------------------- |\n| `schemeH` | `string` | Base hue for the color scheme (0-360) | `--bulma-scheme-h` |\n| `schemeS` | `string` | Base saturation for the color scheme (percentage) | `--bulma-scheme-s` |\n| `lightL` | `string` | Lightness value for light backgrounds | `--bulma-light-l` |\n| `lightInvertL` | `string` | Inverted lightness for light backgrounds | `--bulma-light-invert-l` |\n| `darkL` | `string` | Lightness value for dark backgrounds | `--bulma-dark-l` |\n| `darkInvertL` | `string` | Inverted lightness for dark backgrounds | `--bulma-dark-invert-l` |\n| `softL` | `string` | Lightness value for soft colors | `--bulma-soft-l` |\n| `boldL` | `string` | Lightness value for bold colors | `--bulma-bold-l` |\n| `softInvertL` | `string` | Inverted lightness for soft colors | `--bulma-soft-invert-l` |\n| `boldInvertL` | `string` | Inverted lightness for bold colors | `--bulma-bold-invert-l` |\n| `hoverBackgroundLDelta` | `string` | Lightness delta for hover background states | `--bulma-hover-background-l-delta` |\n| `activeBackgroundLDelta` | `string` | Lightness delta for active background states | `--bulma-active-background-l-delta` |\n| `hoverBorderLDelta` | `string` | Lightness delta for hover border states | `--bulma-hover-border-l-delta` |\n| `activeBorderLDelta` | `string` | Lightness delta for active border states | `--bulma-active-border-l-delta` |\n| `hoverColorLDelta` | `string` | Lightness delta for hover text color states | `--bulma-hover-color-l-delta` |\n| `activeColorLDelta` | `string` | Lightness delta for active text color states | `--bulma-active-color-l-delta` |\n| `hoverShadowADelta` | `string` | Alpha delta for hover shadow states | `--bulma-hover-shadow-a-delta` |\n| `activeShadowADelta` | `string` | Alpha delta for active shadow states | `--bulma-active-shadow-a-delta` |\n\n#### Color Variables\n\n| Prop | Type | Description | CSS Variable |\n| ---------- | -------- | ------------------------------------- | ------------------- |\n| `primaryH` | `string` | Primary color hue (0-360) | `--bulma-primary-h` |\n| `primaryS` | `string` | Primary color saturation (percentage) | `--bulma-primary-s` |\n| `primaryL` | `string` | Primary color lightness (percentage) | `--bulma-primary-l` |\n| `linkH` | `string` | Link color hue (0-360) | `--bulma-link-h` |\n| `linkS` | `string` | Link color saturation (percentage) | `--bulma-link-s` |\n| `linkL` | `string` | Link color lightness (percentage) | `--bulma-link-l` |\n| `infoH` | `string` | Info color hue (0-360) | `--bulma-info-h` |\n| `infoS` | `string` | Info color saturation (percentage) | `--bulma-info-s` |\n| `infoL` | `string` | Info color lightness (percentage) | `--bulma-info-l` |\n| `successH` | `string` | Success color hue (0-360) | `--bulma-success-h` |\n| `successS` | `string` | Success color saturation (percentage) | `--bulma-success-s` |\n| `successL` | `string` | Success color lightness (percentage) | `--bulma-success-l` |\n| `warningH` | `string` | Warning color hue (0-360) | `--bulma-warning-h` |\n| `warningS` | `string` | Warning color saturation (percentage) | `--bulma-warning-s` |\n| `warningL` | `string` | Warning color lightness (percentage) | `--bulma-warning-l` |\n| `dangerH` | `string` | Danger color hue (0-360) | `--bulma-danger-h` |\n| `dangerS` | `string` | Danger color saturation (percentage) | `--bulma-danger-s` |\n| `dangerL` | `string` | Danger color lightness (percentage) | `--bulma-danger-l` |\n\n#### Shadow Variables\n\nReachable only through `bulmaVars` — there is no individual prop, since `shadow` already names\nthe `BulmaOtherProps` `shadowless` helper class. As noted above, `--bulma-shadow` is the one\noverride that cascades into `.box`/`.card`/`.dropdown`/`.panel` shadows.\n\n| CSS Variable | Description |\n| ------------------ | ----------------------------------------------------------------------------------------------------- |\n| `--bulma-shadow-h` | Shadow color hue (0-360) |\n| `--bulma-shadow-s` | Shadow color saturation (percentage) |\n| `--bulma-shadow-l` | Shadow color lightness (percentage) |\n| `--bulma-shadow` | The full shadow value that `.box`/`.card`/`.dropdown`/`.panel` derive their own shadow variables from |\n\n#### Complete CSS Variables List\n\nThese are the keys `bulmaVars` accepts, in addition to the scheme, color, and shadow variables\nabove.\n\nBulma declares many per-component variables (`--bulma-card-*`, `--bulma-title-*`,\n`--bulma-box-*`, …) on the component's own selector. That declaration beats a value the\ncomponent would inherit from a `Theme` wrapper, the same way the shadow note in\n[CSS Variable Naming](https://bestax.io/docs/api/helpers/theme#css-variable-naming) describes, so check where Bulma declares a variable\nbefore relying on a `Theme` override of it.\n\n**Scheme Surface Variables:**\n\n- `--bulma-scheme-main`, `--bulma-scheme-main-bis`, `--bulma-scheme-main-ter`\n- `--bulma-scheme-invert`, `--bulma-scheme-invert-bis`, `--bulma-scheme-invert-ter`\n\n**Typography Variables:**\n\n- `--bulma-family-primary`, `--bulma-family-secondary`, `--bulma-family-code`\n- `--bulma-size-small`, `--bulma-size-normal`, `--bulma-size-medium`, `--bulma-size-large`\n- `--bulma-weight-light`, `--bulma-weight-normal`, `--bulma-weight-medium`, `--bulma-weight-semibold`, `--bulma-weight-bold`, `--bulma-weight-extrabold`\n- `--bulma-body-background-color`, `--bulma-body-size`, `--bulma-body-min-width`, `--bulma-body-rendering`\n- `--bulma-body-family`, `--bulma-body-overflow-x`, `--bulma-body-overflow-y`, `--bulma-body-color`\n- `--bulma-body-font-size`, `--bulma-body-weight`, `--bulma-body-line-height`\n- `--bulma-code-family`, `--bulma-code-padding`, `--bulma-code-weight`, `--bulma-code-size`\n- `--bulma-small-font-size`, `--bulma-hr-background-color`, `--bulma-hr-height`, `--bulma-hr-margin`\n- `--bulma-strong-color`, `--bulma-strong-weight`, `--bulma-pre-font-size`, `--bulma-pre-padding`, `--bulma-pre-code-font-size`\n\n**Layout & Spacing Variables:**\n\n- `--bulma-block-spacing`, `--bulma-duration`, `--bulma-easing`, `--bulma-speed`\n- `--bulma-radius-small`, `--bulma-radius`, `--bulma-radius-medium`, `--bulma-radius-large`, `--bulma-radius-rounded`\n- `--bulma-arrow-color`, `--bulma-loading-color`\n- `--bulma-column-gap`, `--bulma-grid-gap`, `--bulma-grid-column-count`, `--bulma-grid-column-min`\n- `--bulma-grid-cell-column-span`, `--bulma-grid-cell-column-start`\n\n**Box Variables:**\n\n- `--bulma-box-background-color`, `--bulma-box-color`, `--bulma-box-radius`, `--bulma-box-shadow`\n- `--bulma-box-padding`, `--bulma-box-link-hover-shadow`, `--bulma-box-link-active-shadow`\n\n**Breadcrumb Variables:**\n\n- `--bulma-breadcrumb-item-color`, `--bulma-breadcrumb-item-hover-color`, `--bulma-breadcrumb-item-active-color`\n- `--bulma-breadcrumb-item-padding-vertical`, `--bulma-breadcrumb-item-padding-horizontal`\n- `--bulma-breadcrumb-item-separator-color`\n\n**Card Variables:**\n\n- `--bulma-card-color`, `--bulma-card-background-color`, `--bulma-card-shadow`, `--bulma-card-radius`\n- `--bulma-card-header-background-color`, `--bulma-card-header-color`, `--bulma-card-header-padding`\n- `--bulma-card-header-shadow`, `--bulma-card-header-weight`\n- `--bulma-card-content-background-color`, `--bulma-card-content-padding`\n- `--bulma-card-footer-background-color`, `--bulma-card-footer-border-top`, `--bulma-card-footer-padding`\n- `--bulma-card-media-margin`\n\n**Dropdown Variables:**\n\n- `--bulma-dropdown-menu-min-width`, `--bulma-dropdown-content-background-color`\n- `--bulma-dropdown-content-offset`, `--bulma-dropdown-content-padding-bottom`\n- `--bulma-dropdown-content-padding-top`, `--bulma-dropdown-content-radius`\n- `--bulma-dropdown-content-shadow`, `--bulma-dropdown-content-z`\n- `--bulma-dropdown-item-h`, `--bulma-dropdown-item-s`, `--bulma-dropdown-item-l`\n- `--bulma-dropdown-item-background-l`, `--bulma-dropdown-item-background-l-delta`\n- `--bulma-dropdown-item-hover-background-l-delta`, `--bulma-dropdown-item-active-background-l-delta`\n- `--bulma-dropdown-item-color-l`, `--bulma-dropdown-item-selected-h`\n- `--bulma-dropdown-item-selected-s`, `--bulma-dropdown-item-selected-l`\n- `--bulma-dropdown-item-selected-background-l`, `--bulma-dropdown-item-selected-color-l`\n- `--bulma-dropdown-divider-background-color`\n\n**Input Variables:**\n\n- `--bulma-input-h`, `--bulma-input-s`, `--bulma-input-l`, `--bulma-input-border-style`\n- `--bulma-input-border-l`, `--bulma-input-border-l-delta`, `--bulma-input-hover-border-l-delta`\n- `--bulma-input-active-border-l-delta`, `--bulma-input-focus-h`, `--bulma-input-focus-s`\n- `--bulma-input-focus-l`, `--bulma-input-focus-shadow-size`, `--bulma-input-focus-shadow-alpha`\n- `--bulma-input-color-l`, `--bulma-input-background-l`, `--bulma-input-background-l-delta`\n- `--bulma-input-height`, `--bulma-input-shadow`, `--bulma-input-placeholder-color`\n- `--bulma-input-disabled-color`, `--bulma-input-disabled-background-color`\n- `--bulma-input-disabled-border-color`, `--bulma-input-disabled-placeholder-color`\n- `--bulma-input-arrow`, `--bulma-input-icon-color`, `--bulma-input-icon-hover-color`\n- `--bulma-input-icon-focus-color`, `--bulma-input-radius`\n\n**Menu Variables:**\n\n- `--bulma-menu-item-h`, `--bulma-menu-item-s`, `--bulma-menu-item-l`\n- `--bulma-menu-item-background-l`, `--bulma-menu-item-background-l-delta`\n- `--bulma-menu-item-hover-background-l-delta`, `--bulma-menu-item-active-background-l-delta`\n- `--bulma-menu-item-color-l`, `--bulma-menu-item-radius`\n- `--bulma-menu-item-selected-h`, `--bulma-menu-item-selected-s`, `--bulma-menu-item-selected-l`\n- `--bulma-menu-item-selected-background-l`, `--bulma-menu-item-selected-color-l`\n- `--bulma-menu-list-border-left`, `--bulma-menu-list-line-height`, `--bulma-menu-list-link-padding`\n- `--bulma-menu-nested-list-margin`, `--bulma-menu-nested-list-padding-left`\n- `--bulma-menu-label-color`, `--bulma-menu-label-font-size`, `--bulma-menu-label-letter-spacing`\n- `--bulma-menu-label-spacing`\n\n**Message Variables:**\n\n- `--bulma-message-h`, `--bulma-message-s`, `--bulma-message-background-l`\n- `--bulma-message-border-l`, `--bulma-message-border-l-delta`, `--bulma-message-border-style`\n- `--bulma-message-border-width`, `--bulma-message-color-l`, `--bulma-message-radius`\n- `--bulma-message-header-weight`, `--bulma-message-header-padding`, `--bulma-message-header-radius`\n- `--bulma-message-header-body-border-width`, `--bulma-message-header-background-l`\n- `--bulma-message-header-color-l`, `--bulma-message-body-border-width`\n- `--bulma-message-body-color`, `--bulma-message-body-padding`, `--bulma-message-body-radius`\n- `--bulma-message-body-pre-code-background-color`\n\n**Modal Variables:**\n\n- `--bulma-modal-z`, `--bulma-modal-background-background-color`, `--bulma-modal-content-width`\n- `--bulma-modal-content-margin-mobile`, `--bulma-modal-content-spacing-mobile`\n- `--bulma-modal-content-spacing-tablet`, `--bulma-modal-close-dimensions`\n- `--bulma-modal-close-right`, `--bulma-modal-close-top`, `--bulma-modal-card-spacing`\n- `--bulma-modal-card-head-background-color`, `--bulma-modal-card-head-padding`\n- `--bulma-modal-card-head-radius`, `--bulma-modal-card-title-color`\n- `--bulma-modal-card-title-line-height`, `--bulma-modal-card-title-size`\n- `--bulma-modal-card-foot-background-color`, `--bulma-modal-card-foot-radius`\n- `--bulma-modal-card-body-background-color`, `--bulma-modal-card-body-padding`\n\n**Navbar Variables:**\n\n- `--bulma-navbar-h`, `--bulma-navbar-s`, `--bulma-navbar-l`, `--bulma-navbar-background-color`\n- `--bulma-navbar-box-shadow-size`, `--bulma-navbar-box-shadow-color`\n- `--bulma-navbar-padding-vertical`, `--bulma-navbar-padding-horizontal`\n- `--bulma-navbar-z`, `--bulma-navbar-fixed-z`, `--bulma-navbar-item-background-a`\n- `--bulma-navbar-item-background-l`, `--bulma-navbar-item-background-l-delta`\n- `--bulma-navbar-item-hover-background-l-delta`, `--bulma-navbar-item-active-background-l-delta`\n- `--bulma-navbar-item-color-l`, `--bulma-navbar-item-selected-h`\n- `--bulma-navbar-item-selected-s`, `--bulma-navbar-item-selected-l`\n- `--bulma-navbar-item-selected-background-l`, `--bulma-navbar-item-selected-color-l`\n- `--bulma-navbar-item-img-max-height`, `--bulma-navbar-burger-color`\n- `--bulma-navbar-tab-hover-background-color`, `--bulma-navbar-tab-hover-border-bottom-color`\n- `--bulma-navbar-tab-active-color`, `--bulma-navbar-tab-active-background-color`\n- `--bulma-navbar-tab-active-border-bottom-color`, `--bulma-navbar-tab-active-border-bottom-style`\n- `--bulma-navbar-tab-active-border-bottom-width`, `--bulma-navbar-dropdown-background-color`\n- `--bulma-navbar-dropdown-border-l`, `--bulma-navbar-dropdown-border-color`\n- `--bulma-navbar-dropdown-border-style`, `--bulma-navbar-dropdown-border-width`\n- `--bulma-navbar-dropdown-offset`, `--bulma-navbar-dropdown-arrow`\n- `--bulma-navbar-dropdown-radius`, `--bulma-navbar-dropdown-z`\n- `--bulma-navbar-dropdown-boxed-radius`, `--bulma-navbar-dropdown-boxed-shadow`\n- `--bulma-navbar-dropdown-item-h`, `--bulma-navbar-dropdown-item-s`\n- `--bulma-navbar-dropdown-item-l`, `--bulma-navbar-dropdown-item-background-l`\n- `--bulma-navbar-dropdown-item-color-l`, `--bulma-navbar-divider-background-l`\n- `--bulma-navbar-divider-height`, `--bulma-navbar-bottom-box-shadow-size`\n\n**Notification Variables:**\n\n- `--bulma-notification-h`, `--bulma-notification-s`, `--bulma-notification-background-l`\n- `--bulma-notification-color-l`, `--bulma-notification-code-background-color`\n- `--bulma-notification-radius`, `--bulma-notification-padding`\n\n**Pagination Variables:**\n\n- `--bulma-pagination-margin`, `--bulma-pagination-min-width`\n- `--bulma-pagination-item-h`, `--bulma-pagination-item-s`, `--bulma-pagination-item-l`\n- `--bulma-pagination-item-background-l-delta`, `--bulma-pagination-item-hover-background-l-delta`\n- `--bulma-pagination-item-active-background-l-delta`, `--bulma-pagination-item-border-style`\n- `--bulma-pagination-item-border-width`, `--bulma-pagination-item-border-l`\n- `--bulma-pagination-item-border-l-delta`, `--bulma-pagination-item-hover-border-l-delta`\n- `--bulma-pagination-item-active-border-l-delta`, `--bulma-pagination-item-focus-border-l-delta`\n- `--bulma-pagination-item-color-l`, `--bulma-pagination-item-font-size`\n- `--bulma-pagination-item-margin`, `--bulma-pagination-item-padding-left`\n- `--bulma-pagination-item-padding-right`, `--bulma-pagination-item-outer-shadow-h`\n- `--bulma-pagination-item-outer-shadow-s`, `--bulma-pagination-item-outer-shadow-l`\n- `--bulma-pagination-item-outer-shadow-a`, `--bulma-pagination-nav-padding-left`\n- `--bulma-pagination-nav-padding-right`, `--bulma-pagination-disabled-color`\n- `--bulma-pagination-disabled-background-color`, `--bulma-pagination-disabled-border-color`\n- `--bulma-pagination-current-color`, `--bulma-pagination-current-background-color`\n- `--bulma-pagination-current-border-color`, `--bulma-pagination-ellipsis-color`\n- `--bulma-pagination-shadow-inset`, `--bulma-pagination-selected-item-h`\n- `--bulma-pagination-selected-item-s`, `--bulma-pagination-selected-item-l`\n- `--bulma-pagination-selected-item-background-l`, `--bulma-pagination-selected-item-border-l`\n- `--bulma-pagination-selected-item-color-l`\n\n**Panel Variables:**\n\n- `--bulma-panel-margin`, `--bulma-panel-item-border`, `--bulma-panel-radius`, `--bulma-panel-shadow`\n- `--bulma-panel-heading-line-height`, `--bulma-panel-heading-padding`, `--bulma-panel-heading-radius`\n- `--bulma-panel-heading-size`, `--bulma-panel-heading-weight`, `--bulma-panel-tabs-font-size`\n- `--bulma-panel-tab-border-bottom-color`, `--bulma-panel-tab-border-bottom-style`\n- `--bulma-panel-tab-border-bottom-width`, `--bulma-panel-tab-active-color`\n- `--bulma-panel-list-item-color`, `--bulma-panel-list-item-hover-color`\n- `--bulma-panel-block-color`, `--bulma-panel-block-hover-background-color`\n- `--bulma-panel-block-active-border-left-color`, `--bulma-panel-block-active-color`\n- `--bulma-panel-block-active-icon-color`, `--bulma-panel-icon-color`\n\n**Progress Variables:**\n\n- `--bulma-progress-border-radius`, `--bulma-progress-bar-background-color`\n- `--bulma-progress-value-background-color`, `--bulma-progress-indeterminate-duration`\n\n**Skeleton Variables:**\n\n- `--bulma-skeleton-background`, `--bulma-skeleton-radius`, `--bulma-skeleton-block-min-height`\n- `--bulma-skeleton-lines-gap`, `--bulma-skeleton-line-height`\n\n**Table Variables:**\n\n- `--bulma-table-color`, `--bulma-table-background-color`, `--bulma-table-cell-border-color`\n- `--bulma-table-cell-border-style`, `--bulma-table-cell-border-width`, `--bulma-table-cell-padding`\n- `--bulma-table-cell-heading-color`, `--bulma-table-cell-text-align`\n- `--bulma-table-head-cell-border-width`, `--bulma-table-head-cell-color`\n- `--bulma-table-foot-cell-border-width`, `--bulma-table-foot-cell-color`\n- `--bulma-table-head-background-color`, `--bulma-table-body-background-color`\n- `--bulma-table-foot-background-color`, `--bulma-table-row-hover-background-color`\n- `--bulma-table-row-active-background-color`, `--bulma-table-row-active-color`\n- `--bulma-table-striped-row-even-background-color`, `--bulma-table-striped-row-even-hover-background-color`\n\n**Tabs Variables:**\n\n- `--bulma-tabs-border-bottom-color`, `--bulma-tabs-border-bottom-style`, `--bulma-tabs-border-bottom-width`\n- `--bulma-tabs-link-color`, `--bulma-tabs-link-hover-border-bottom-color`, `--bulma-tabs-link-hover-color`\n- `--bulma-tabs-link-active-border-bottom-color`, `--bulma-tabs-link-active-color`, `--bulma-tabs-link-padding`\n- `--bulma-tabs-boxed-link-radius`, `--bulma-tabs-boxed-link-hover-background-color`\n- `--bulma-tabs-boxed-link-hover-border-bottom-color`, `--bulma-tabs-boxed-link-active-background-color`\n- `--bulma-tabs-boxed-link-active-border-color`, `--bulma-tabs-boxed-link-active-border-bottom-color`\n- `--bulma-tabs-toggle-link-border-color`, `--bulma-tabs-toggle-link-border-style`\n- `--bulma-tabs-toggle-link-border-width`, `--bulma-tabs-toggle-link-hover-background-color`\n- `--bulma-tabs-toggle-link-hover-border-color`, `--bulma-tabs-toggle-link-radius`\n- `--bulma-tabs-toggle-link-active-background-color`, `--bulma-tabs-toggle-link-active-border-color`\n- `--bulma-tabs-toggle-link-active-color`\n\n**Tag Variables:**\n\n- `--bulma-tag-h`, `--bulma-tag-s`, `--bulma-tag-background-l`, `--bulma-tag-background-l-delta`\n- `--bulma-tag-hover-background-l-delta`, `--bulma-tag-active-background-l-delta`\n- `--bulma-tag-color-l`, `--bulma-tag-radius`, `--bulma-tag-delete-margin`\n\n**Title & Subtitle Variables:**\n\n- `--bulma-title-color`, `--bulma-title-family`, `--bulma-title-size`, `--bulma-title-weight`\n- `--bulma-title-line-height`, `--bulma-title-strong-color`, `--bulma-title-strong-weight`\n- `--bulma-title-sub-size`, `--bulma-title-sup-size`\n- `--bulma-subtitle-color`, `--bulma-subtitle-family`, `--bulma-subtitle-size`, `--bulma-subtitle-weight`\n- `--bulma-subtitle-line-height`, `--bulma-subtitle-strong-color`, `--bulma-subtitle-strong-weight`\n\n**Content Variables:**\n\n- `--bulma-content-heading-color`, `--bulma-content-heading-weight`, `--bulma-content-heading-line-height`\n- `--bulma-content-block-margin-bottom`, `--bulma-content-blockquote-background-color`\n- `--bulma-content-blockquote-border-left`, `--bulma-content-blockquote-padding`, `--bulma-content-pre-padding`\n- `--bulma-content-table-cell-border`, `--bulma-content-table-cell-border-width`\n- `--bulma-content-table-cell-padding`, `--bulma-content-table-cell-heading-color`\n- `--bulma-content-table-head-cell-border-width`, `--bulma-content-table-head-cell-color`\n- `--bulma-content-table-body-last-row-cell-border-bottom-width`\n- `--bulma-content-table-foot-cell-border-width`, `--bulma-content-table-foot-cell-color`\n\n**Control Variables:**\n\n- `--bulma-control-radius`, `--bulma-control-radius-small`, `--bulma-control-border-width`\n- `--bulma-control-height`, `--bulma-control-line-height`, `--bulma-control-padding-vertical`\n- `--bulma-control-padding-horizontal`, `--bulma-control-size`, `--bulma-control-focus-shadow-l`\n\n**Delete Variables:**\n\n- `--bulma-delete-dimensions`, `--bulma-delete-background-l`, `--bulma-delete-background-alpha`\n- `--bulma-delete-color`\n\n**File Variables:**\n\n- `--bulma-file-radius`, `--bulma-file-name-border-color`, `--bulma-file-name-border-style`\n- `--bulma-file-name-border-width`, `--bulma-file-name-max-width`\n- `--bulma-file-h`, `--bulma-file-s`, `--bulma-file-background-l`, `--bulma-file-background-l-delta`\n- `--bulma-file-hover-background-l-delta`, `--bulma-file-active-background-l-delta`\n- `--bulma-file-border-l`, `--bulma-file-border-l-delta`, `--bulma-file-hover-border-l-delta`\n- `--bulma-file-active-border-l-delta`, `--bulma-file-cta-color-l`, `--bulma-file-name-color-l`\n- `--bulma-file-color-l-delta`, `--bulma-file-hover-color-l-delta`, `--bulma-file-active-color-l-delta`\n\n**Footer Variables:**\n\n- `--bulma-footer-background-color`, `--bulma-footer-color`, `--bulma-footer-padding`\n\n**Hero Variables:**\n\n- `--bulma-hero-body-padding`, `--bulma-hero-body-padding-tablet`, `--bulma-hero-body-padding-small`\n- `--bulma-hero-body-padding-medium`, `--bulma-hero-body-padding-large`\n\n**Icon Variables:**\n\n- `--bulma-icon-dimensions`, `--bulma-icon-dimensions-small`, `--bulma-icon-dimensions-medium`\n- `--bulma-icon-dimensions-large`, `--bulma-icon-text-spacing`\n\n**Media Variables:**\n\n- `--bulma-media-border-color`, `--bulma-media-border-size`, `--bulma-media-spacing`\n- `--bulma-media-spacing-large`, `--bulma-media-content-spacing`, `--bulma-media-level-1-spacing`\n- `--bulma-media-level-1-content-spacing`, `--bulma-media-level-2-spacing`\n\n**Section Variables:**\n\n- `--bulma-section-padding`, `--bulma-section-padding-desktop`, `--bulma-section-padding-medium`\n- `--bulma-section-padding-large`\n\n**Burger Variables:**\n\n- `--bulma-burger-h`, `--bulma-burger-s`, `--bulma-burger-l`, `--bulma-burger-border-radius`\n- `--bulma-burger-gap`, `--bulma-burger-item-height`, `--bulma-burger-item-width`\n\nThe Theme component also supports all [Bulma helper class props](https://bestax.io/docs/api/helpers/usebulmaclasses) for styling the wrapper element."
470
+ "doc": "# Theme\n\n## Overview\n\nThe `Theme` component provides a powerful way to customize Bulma's appearance using CSS custom properties (CSS variables). It allows you to override Bulma's design tokens like colors, spacing, typography, and other visual properties either globally or locally within specific component trees. The Theme component supports both CSS variable injection via props and direct CSS variable objects.\n\n---\n\n## Import\n\n```tsx\nimport { Theme } from '@allxsmith/bestax-bulma';\n```\n\n---\n\n## Usage\n\n### Basic Theme Customization\n\n```tsx\nfunction BasicThemeCustomization() {\n return (\n <Theme primaryH=\"270\" primaryS=\"100%\" primaryL=\"50%\">\n <Box p=\"4\">\n <Button color=\"primary\">Purple Primary Button</Button>\n </Box>\n </Theme>\n );\n}\n```\n\n### Global Theme (Root Level)\n\n```tsx\nfunction GlobalTheme() {\n return (\n <Theme isRoot={true} primaryH=\"270\" primaryS=\"100%\" primaryL=\"50%\">\n <div>\n <Button color=\"primary\">Global Purple Theme</Button>\n <Button color=\"info\">Info Button</Button>\n </div>\n </Theme>\n );\n}\n```\n\n### Dark Mode\n\nThe `colorMode` prop sets Bulma's `data-theme` on `<html>`, flipping the light/dark scheme globally.\nUse `'system'` to follow the OS preference.\n\nA prefixed stylesheet reads Bulma's prefixed attribute instead (`data-bestax-theme` for\n`bestax-prefixed.css` and `bestax-no-helpers-prefixed.css`). Under a `ConfigProvider`\n`classPrefix`, `colorMode` writes that attribute as well as `data-theme`, so it works with the\nprefixed builds.\n\n:::warning Single-mode designs should pin `colorMode`\n\nOmitting `colorMode` preserves whatever `data-theme` is already set — and when nothing has set\none (the usual case), the scheme follows the visitor's OS: Bulma's text colors flip on a\ndark-mode machine even if your design is light-only, breaking contrast against any fixed custom\nbackgrounds. If you support only one mode, pin it (`<Theme isRoot colorMode=\"light\">`); if you\nsupport both, don't hardcode surface/text colors — see\n[Dark Mode & Contrast](https://bestax.io/docs/guides/features/css-variables#dark-mode--contrast).\n\n:::\n\n```tsx\nimport { useState } from 'react';\n\nfunction App() {\n const [mode, setMode] = useState<'light' | 'dark' | 'system'>('system');\n return (\n <Theme isRoot colorMode={mode}>\n <Button color=\"primary\" onClick={() => setMode('dark')}>\n Dark\n </Button>\n <Button color=\"primary\" onClick={() => setMode('light')}>\n Light\n </Button>\n <Button onClick={() => setMode('system')}>System</Button>\n </Theme>\n );\n}\n```\n\n### Local Theme (Component Level)\n\n```tsx\nfunction LocalTheme() {\n return (\n <div>\n <Button color=\"primary\">Standard Primary</Button>\n\n <Theme primaryH=\"120\" primaryS=\"100%\" primaryL=\"40%\">\n <Button color=\"primary\">Green Primary</Button>\n </Theme>\n\n <Button color=\"primary\">Standard Primary Again</Button>\n </div>\n );\n}\n```\n\n### Using CSS Variables Object\n\n```tsx\nfunction SunsetTheme() {\n const sunsetTheme = {\n '--bulma-scheme-h': '18',\n '--bulma-scheme-s': '90%',\n '--bulma-light-l': '85%',\n '--bulma-dark-l': '20%',\n '--bulma-primary-h': '25',\n '--bulma-primary-s': '85%',\n '--bulma-primary-l': '55%',\n };\n\n return (\n <Theme bulmaVars={sunsetTheme} isRoot>\n <Box p=\"4\">\n <Title>Sunset Theme</Title>\n <Button color=\"primary\">Sunset Button</Button>\n <Button color=\"info\">Info Button</Button>\n </Box>\n </Theme>\n );\n}\n```\n\n### Advanced CSS Variables Usage\n\nBulma derives the body font and the control radius from these variables at `:root`, so set\ntypography and radius on an `isRoot` theme.\n\n```tsx\nfunction TypographyTheme() {\n const typographyTheme = {\n '--bulma-family-primary': '\"Helvetica Neue\", sans-serif',\n '--bulma-family-code': '\"Fira Code\", monospace',\n '--bulma-size-normal': '16px',\n '--bulma-weight-bold': '700',\n };\n\n return (\n <Theme isRoot bulmaVars={typographyTheme}>\n <Title>Custom Typography</Title>\n <SubTitle>With custom fonts and weights</SubTitle>\n </Theme>\n );\n}\n```\n\n```tsx\nfunction RadiusTheme() {\n // `radius` is the border radius helper, as on every component, so the radius scale goes through bulmaVars\n const radii = {\n '--bulma-radius-small': '6px',\n '--bulma-radius': '12px',\n '--bulma-radius-medium': '16px',\n '--bulma-radius-large': '20px',\n };\n\n return (\n <Theme isRoot bulmaVars={radii}>\n <Button color=\"primary\">Rounder Button</Button>\n </Theme>\n );\n}\n```\n\nSpacing that Bulma reads where it is used, such as the block and column gaps, works on a scoped\ntheme:\n\n```tsx\nfunction SpacingTheme() {\n const spacingTheme = {\n '--bulma-block-spacing': '2rem',\n '--bulma-column-gap': '1rem',\n };\n\n return (\n <Theme bulmaVars={spacingTheme}>\n <Columns>\n <Column>\n <Box>Wider column gap</Box>\n <Box>Wider block spacing</Box>\n </Column>\n <Column>\n <Box>Wider column gap</Box>\n </Column>\n </Columns>\n </Theme>\n );\n}\n```\n\n### Complete Color Scheme\n\n```tsx\nfunction ForestTheme() {\n const forestScheme = {\n schemeH: '150', // forest green hue\n schemeS: '50%',\n lightL: '80%',\n lightInvertL: '20%',\n darkL: '18%',\n darkInvertL: '85%',\n softL: '55%',\n boldL: '35%',\n primaryH: '160',\n primaryS: '60%',\n primaryL: '45%',\n linkH: '155',\n linkS: '65%',\n linkL: '40%',\n successH: '120',\n successS: '70%',\n successL: '45%',\n warningH: '45',\n warningS: '85%',\n warningL: '55%',\n dangerH: '355',\n dangerS: '75%',\n dangerL: '50%',\n hoverBackgroundLDelta: '4%',\n activeBackgroundLDelta: '8%',\n };\n\n return (\n <Theme {...forestScheme} isRoot>\n <Box p=\"4\">\n <Title>Forest Theme</Title>\n <Button color=\"primary\">Forest Primary</Button>\n <Button color=\"success\">Success</Button>\n <Button color=\"info\">Info</Button>\n <Button color=\"warning\">Warning</Button>\n <Button color=\"danger\">Danger</Button>\n </Box>\n </Theme>\n );\n}\n```\n\n### Theme with Styling\n\n```tsx\nfunction StyledTheme() {\n return (\n <Theme\n className=\"custom-theme-wrapper\"\n primaryH=\"45\"\n primaryS=\"100%\"\n primaryL=\"50%\"\n p=\"5\"\n m=\"3\"\n textAlign=\"centered\"\n >\n <Title>Styled Theme Container</Title>\n <Button color=\"primary\">Styled Button</Button>\n </Theme>\n );\n}\n```\n\n### Nested Themes\n\n```tsx\nfunction NestedThemes() {\n return (\n <Theme primaryH=\"210\" primaryS=\"60%\" primaryL=\"45%\" p=\"4\">\n <Title>Outer Theme (Blue)</Title>\n <Button color=\"primary\">Blue Primary</Button>\n\n <Theme primaryH=\"120\" primaryS=\"70%\" primaryL=\"40%\" p=\"3\" mt=\"4\">\n <Title size=\"4\">Inner Theme (Green)</Title>\n <Button color=\"primary\">Green Primary</Button>\n </Theme>\n\n <Button color=\"primary\" mt=\"3\">\n Blue Primary Again\n </Button>\n </Theme>\n );\n}\n```\n\n### Theme with ConfigProvider\n\n```tsx\nfunction PrefixedTheme() {\n return (\n <ConfigProvider classPrefix=\"bestax-\">\n <Theme primaryH=\"300\" primaryS=\"80%\" primaryL=\"50%\" isRoot>\n <Button color=\"primary\">Prefixed & Themed Button</Button>\n </Theme>\n </ConfigProvider>\n );\n}\n```\n\n---\n\n## Best Practices\n\n### Global vs Local Themes\n\n- Use `isRoot={true}` for application-wide themes that should affect all components\n- Use local themes (default `isRoot={false}`) for component-specific styling or theme variations\n- Local themes inherit from parent themes and can override specific variables\n- Several `isRoot` themes can be mounted at once, such as a color theme at the app root and a\n typography theme next to it. Each adds its own variables at `:root`; where two set the same\n variable, the inner or later-mounted one wins, as with nested local themes, and unmounting one\n removes only its own variables\n\n### CSS Variable Naming\n\n- All Bulma CSS variables follow the `--bulma-*` naming convention\n- Named props exist for the scheme and color variables only (like `primaryH`); every other variable, typography and radius included, goes through `bulmaVars`\n- `bulmaVars` keys are the full variable names (`'--bulma-family-primary'`), not camelCase\n- `bulmaVars` also accepts the scheme surface variables — `--bulma-scheme-main`, `--bulma-scheme-main-bis`, `--bulma-scheme-main-ter`, `--bulma-scheme-invert`, `--bulma-scheme-invert-bis`, `--bulma-scheme-invert-ter` — so overriding `--bulma-scheme-main-bis`/`-ter` re-tints every scheme-background band (e.g., `<Section bgColor=\"scheme-main-bis\">`) at once\n- `bulmaVars` also accepts `--bulma-shadow` (plus `--bulma-shadow-h`/`-s`/`-l`) — the upstream token that `.box`, `.card`, `.dropdown`, and `.panel` derive their own shadow variables from. Those selectors re-declare their derived variable (e.g. `--bulma-box-shadow`) on themselves, so setting it from an ancestor `Theme` is only inherited and never wins; overriding `--bulma-shadow` itself is the one override that cascades. There is no individual `shadow` prop for this — `shadow` already names the unrelated `shadowless` helper class — so reach it through `bulmaVars`\n\n### Performance Considerations\n\n- Prefer setting themes at higher levels in your component tree rather than deeply nested\n- CSS variables are inherited, so child themes only need to override specific variables\n\n### Color System\n\n- Bulma uses HSL (Hue, Saturation, Lightness) for its color system\n- Hue values range from 0-360 (color wheel degrees)\n- Saturation and Lightness are typically percentages (e.g., '50%')\n- The scheme variables control the base color relationships\n\n---\n\n## API Reference\n\n### Named Props and `bulmaVars`\n\n`ThemeProps` has a named, camelCase prop for each scheme and color variable, listed under\n[CSS Variable Props](https://bestax.io/docs/api/helpers/theme#css-variable-props). Each converts to its CSS variable by the same rule:\n\n```\npropName → --bulma-prop-name\n```\n\n- `primaryH` → `--bulma-primary-h`\n- `schemeS` → `--bulma-scheme-s`\n- `lightL` → `--bulma-light-l`\n- `hoverBackgroundLDelta` → `--bulma-hover-background-l-delta`\n\nEvery other variable (typography, radius, spacing, and the rest) has no named prop in\n`ThemeProps` and goes through `bulmaVars`, keyed by its full `--bulma-*` name. JavaScript code\nthat passes one as a camelCase prop (`familyPrimary`) may still render, but it is outside the\ntyped API, so move it to `bulmaVars`:\n\n```tsx\n<Theme\n isRoot\n primaryH=\"350\"\n primaryS=\"73%\"\n primaryL=\"44%\"\n bulmaVars={{\n '--bulma-radius': '2px',\n '--bulma-radius-small': '1px',\n '--bulma-family-primary': '\"IBM Plex Sans\", system-ui, sans-serif',\n '--bulma-family-code': '\"IBM Plex Mono\", ui-monospace, monospace',\n }}\n>\n <App />\n</Theme>\n```\n\nSome of those names are already helper props, so they cannot double as CSS-variable props:\n\n- `radius` is the border radius helper, as on every other component, so `<Theme radius=\"radiusless\">`\n adds `is-radiusless` to the wrapper. On a Theme it also sets `--bulma-radius` to `0`, so what\n is inside the Theme loses its radius too; under `isRoot` it writes that at `:root`, squaring\n everything on the page that takes its radius from it. The sizes (`small`, `normal`, `large`,\n `rounded`) add their `has-radius-*` class to the wrapper and set no variable, so they round\n the wrapper and leave what is inside alone; under `isRoot` there is no wrapper, so they do\n nothing and warn in development. Set any other `--bulma-radius` through\n `bulmaVars`. `<Theme radius=\"2px\" />` is a type error; in JavaScript it still sets\n `--bulma-radius`, which is how the prop used to behave, but that route is deprecated and logs\n a warning in development.\n- `shadow` is the `shadowless` helper. Set `--bulma-shadow` through `bulmaVars`.\n- `columnGap` is the column gap helper, as on every other component, so `<Theme columnGap=\"2\">`\n adds `is-column-gap-2` to the wrapper and sets no variable; under `isRoot` there is no wrapper,\n so it does nothing and warns in development. Set the columns gutter, `--bulma-column-gap`,\n through `bulmaVars`. Before the gap helpers, JavaScript could pass `columnGap` to set that\n variable. A string that is not a gap step (`columnGap=\"1rem\"`) still does, through a\n deprecated route that logs a warning in development, but a gap step does not:\n `columnGap=\"0\"`, which used to zero the gutters inside the Theme, now adds `is-column-gap-0`\n to the wrapper.\n\n### Props vs bulmaVars\n\nYou can set the scheme and color variables in two ways:\n\n1. **Named Props** (recommended where one exists):\n\n ```tsx\n <Theme primaryH=\"270\" primaryS=\"100%\" primaryL=\"50%\">\n <App />\n </Theme>\n ```\n\n2. **bulmaVars Object** (for every other variable, or when you have many to set):\n\n ```tsx\n <Theme\n bulmaVars={{\n '--bulma-primary-h': '270',\n '--bulma-primary-s': '100%',\n '--bulma-primary-l': '50%',\n '--bulma-block-spacing': '2rem',\n }}\n >\n <App />\n </Theme>\n ```\n\n3. **Combined** (props take precedence over bulmaVars):\n ```tsx\n <Theme\n primaryH=\"270\" // This takes precedence\n bulmaVars={{\n '--bulma-primary-h': '180', // This is overridden\n '--bulma-block-spacing': '2rem',\n }}\n >\n <App />\n </Theme>\n ```\n\n### TypeScript Support\n\n`bulmaVars` is typed as a partial record keyed by the union of every variable `Theme` accepts.\nWrite it as an object literal and your editor autocompletes the keys and flags a misspelled or\nunsupported one as a type error. A separately declared object only has to share one valid key\nto typecheck, so a typo in it is silently dropped; annotate it with\n`satisfies ThemeProps['bulmaVars']` to keep the check:\n\n```tsx\n<Theme\n primaryH=\"270\"\n bulmaVars={{\n '--bulma-block-spacing': '2rem', // start typing '--bulma-' to list the rest\n }}\n>\n <App />\n</Theme>\n```\n\n---\n\n## See Also\n\n- [ConfigProvider](https://bestax.io/docs/api/helpers/config) - For class prefixing and configuration\n- [useBulmaClasses](https://bestax.io/docs/api/helpers/usebulmaclasses) - For applying Bulma helper classes\n- [Bulma CSS Variables Documentation](https://bulma.io/documentation/features/css-variables/) - Official Bulma CSS variables reference\n\n---\n\n## Props\n\n| Prop | Type | Description |\n| ----------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `children` | `ReactNode` | The child components to apply the theme to. |\n| `className` | `string` | Additional CSS classes for the theme wrapper. A root theme has no wrapper, so under `isRoot` it does nothing and warns in development. |\n| `isRoot` | `boolean` | When `true`, applies CSS variables globally at `:root` level. When `false` (default), applies variables only to the wrapper div. Several root themes compose; an inner or later-mounted one wins a variable they share. A root theme renders no wrapper, so `className` and the helper props, such as `m` or `shadow`, do nothing on it and warn in development. |\n| `colorMode` | `'light' \\| 'dark' \\| 'system'` | Sets Bulma's light/dark scheme by writing the `data-theme` attribute on `<html>`, plus `data-<prefix>theme` inside a `ConfigProvider` with a `classPrefix`. If the prefix cannot form an attribute name, `'light'` and `'dark'` write only `data-theme` and warn in development. Always global (even on a scoped `Theme`). `'system'` removes them so Bulma follows the OS `prefers-color-scheme`. Omit to leave the current setting untouched. |\n| `bulmaVars` | `ThemeProps['bulmaVars']` | Object mapping Bulma CSS variable names to string values (e.g., `{'--bulma-primary-h': '210'}`). Keys are limited to the `BulmaVars` type; anything else is not applied. A variable Bulma declares on the component itself changes nothing from here and warns in development; see [Which Variables a Theme Can Change](https://bestax.io/docs/api/helpers/theme#which-variables-a-theme-can-change). |\n| `columnGap` | [`BulmaGapStep`](https://bestax.io/docs/api/helpers/valid-values) | The column gap helper: a gap step adds `is-column-gap-<step>` to the wrapper div and sets no variable, so under `isRoot` it does nothing and warns in development. Set `--bulma-column-gap` through `bulmaVars`. Any other string still sets `--bulma-column-gap`, a deprecated route that warns in development. |\n| `radius` | `'radiusless' \\| 'small' \\| 'normal' \\| 'large' \\| 'rounded'` | The border radius helper. `radiusless` adds `is-radiusless` to the wrapper div and sets `--bulma-radius` to `0`, at `:root` under `isRoot`. The sizes add their `has-radius-*` class to the wrapper and set no variable, so under `isRoot` they do nothing and warn in development. Any other string still sets `--bulma-radius`, a deprecated route that warns in development. |\n\n### CSS Variable Props\n\nThe Theme component accepts individual CSS variable props that correspond to Bulma's design tokens. These props are automatically converted to their corresponding CSS custom properties (e.g., `primaryH` → `--bulma-primary-h`).\n\n#### Scheme Variables\n\n| Prop | Type | Description | CSS Variable |\n| ------------------------ | -------- | ------------------------------------------------- | ----------------------------------- |\n| `schemeH` | `string` | Base hue for the color scheme (0-360) | `--bulma-scheme-h` |\n| `schemeS` | `string` | Base saturation for the color scheme (percentage) | `--bulma-scheme-s` |\n| `lightL` | `string` | Lightness value for light backgrounds | `--bulma-light-l` |\n| `lightInvertL` | `string` | Inverted lightness for light backgrounds | `--bulma-light-invert-l` |\n| `darkL` | `string` | Lightness value for dark backgrounds | `--bulma-dark-l` |\n| `darkInvertL` | `string` | Inverted lightness for dark backgrounds | `--bulma-dark-invert-l` |\n| `softL` | `string` | Lightness value for soft colors | `--bulma-soft-l` |\n| `boldL` | `string` | Lightness value for bold colors | `--bulma-bold-l` |\n| `softInvertL` | `string` | Inverted lightness for soft colors | `--bulma-soft-invert-l` |\n| `boldInvertL` | `string` | Inverted lightness for bold colors | `--bulma-bold-invert-l` |\n| `hoverBackgroundLDelta` | `string` | Lightness delta for hover background states | `--bulma-hover-background-l-delta` |\n| `activeBackgroundLDelta` | `string` | Lightness delta for active background states | `--bulma-active-background-l-delta` |\n| `hoverBorderLDelta` | `string` | Lightness delta for hover border states | `--bulma-hover-border-l-delta` |\n| `activeBorderLDelta` | `string` | Lightness delta for active border states | `--bulma-active-border-l-delta` |\n| `hoverColorLDelta` | `string` | Lightness delta for hover text color states | `--bulma-hover-color-l-delta` |\n| `activeColorLDelta` | `string` | Lightness delta for active text color states | `--bulma-active-color-l-delta` |\n| `hoverShadowADelta` | `string` | Alpha delta for hover shadow states | `--bulma-hover-shadow-a-delta` |\n| `activeShadowADelta` | `string` | Alpha delta for active shadow states | `--bulma-active-shadow-a-delta` |\n\n#### Color Variables\n\n| Prop | Type | Description | CSS Variable |\n| ---------- | -------- | ------------------------------------- | ------------------- |\n| `primaryH` | `string` | Primary color hue (0-360) | `--bulma-primary-h` |\n| `primaryS` | `string` | Primary color saturation (percentage) | `--bulma-primary-s` |\n| `primaryL` | `string` | Primary color lightness (percentage) | `--bulma-primary-l` |\n| `linkH` | `string` | Link color hue (0-360) | `--bulma-link-h` |\n| `linkS` | `string` | Link color saturation (percentage) | `--bulma-link-s` |\n| `linkL` | `string` | Link color lightness (percentage) | `--bulma-link-l` |\n| `infoH` | `string` | Info color hue (0-360) | `--bulma-info-h` |\n| `infoS` | `string` | Info color saturation (percentage) | `--bulma-info-s` |\n| `infoL` | `string` | Info color lightness (percentage) | `--bulma-info-l` |\n| `successH` | `string` | Success color hue (0-360) | `--bulma-success-h` |\n| `successS` | `string` | Success color saturation (percentage) | `--bulma-success-s` |\n| `successL` | `string` | Success color lightness (percentage) | `--bulma-success-l` |\n| `warningH` | `string` | Warning color hue (0-360) | `--bulma-warning-h` |\n| `warningS` | `string` | Warning color saturation (percentage) | `--bulma-warning-s` |\n| `warningL` | `string` | Warning color lightness (percentage) | `--bulma-warning-l` |\n| `dangerH` | `string` | Danger color hue (0-360) | `--bulma-danger-h` |\n| `dangerS` | `string` | Danger color saturation (percentage) | `--bulma-danger-s` |\n| `dangerL` | `string` | Danger color lightness (percentage) | `--bulma-danger-l` |\n\n#### Shadow Variables\n\nReachable only through `bulmaVars` — there is no individual prop, since `shadow` already names\nthe `BulmaOtherProps` `shadowless` helper class. As noted above, `--bulma-shadow` is the one\noverride that cascades into `.box`/`.card`/`.dropdown`/`.panel` shadows.\n\n| CSS Variable | Description |\n| ------------------ | ----------------------------------------------------------------------------------------------------- |\n| `--bulma-shadow-h` | Shadow color hue (0-360) |\n| `--bulma-shadow-s` | Shadow color saturation (percentage) |\n| `--bulma-shadow-l` | Shadow color lightness (percentage) |\n| `--bulma-shadow` | The full shadow value that `.box`/`.card`/`.dropdown`/`.panel` derive their own shadow variables from |\n\n#### Which Variables a Theme Can Change\n\n`bulmaVars` takes any key of the `BulmaVars` type. Write it as an object literal and your editor\nlists them as you type `'--bulma-'`. Not every one of those keys does something from a `Theme`,\nthough, and the reason is where Bulma declares the variable.\n\nA `Theme` writes its variables on its wrapper `div`, or at `:root` under `isRoot`. A component\ninherits them from there only if it declares no value of its own, because an element's own\ndeclaration always beats an inherited one.\n\n- **Global variables reach everything under the Theme.** Bulma declares the scheme, color and\n shadow variables above, and the typography, radius, spacing and timing tokens (such as\n `--bulma-family-primary` and `--bulma-radius`), on `:root`. Set them on a `Theme`, or on any\n ancestor. Some of them Bulma also declares again on one component, which keeps its own value\n while everything else takes the Theme's. That component's API page lists the variable as\n declared on it.\n- **Component variables have to be set on the component.** Where Bulma declares a variable on\n the component's own element, as it does `--bulma-card-radius`, `--bulma-tag-h` and\n `--bulma-delete-dimensions`, a value a `Theme` sets never reaches it. Those keys are still\n accepted, so existing code keeps compiling, but they change nothing, and `Theme` logs a\n warning in development naming them. Set them on the component instead, with\n `className` (a class in your own stylesheet) or the `style` prop, or at build time through\n [Sass](https://bestax.io/docs/guides/features/sass-customization).\n\nEach component's API page lists its variables under **CSS & Sass Variables** and says where Bulma\ndeclares them, so check there before reaching for a `Theme`. The\n[shadow note](https://bestax.io/docs/api/helpers/theme#css-variable-naming) above is the same rule in action: `--bulma-box-shadow` lives on\n`.box`, so the way to change box shadows from a `Theme` is the global `--bulma-shadow` they derive\nfrom.\n\nThe Theme component also supports all [Bulma helper class props](https://bestax.io/docs/api/helpers/usebulmaclasses) for styling the wrapper element."
471
471
  }
@@ -195,7 +195,7 @@
195
195
  "name": "label",
196
196
  "type": "React.ReactNode",
197
197
  "default": null,
198
- "description": "Field label. Automatically associated with the input via `htmlFor` — uses your `id` when provided, otherwise a generated one. Not wired in `inline` mode (no visible input to label). Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`).",
198
+ "description": "Field label. Automatically associated with the input via `htmlFor` — uses your `id` when provided, otherwise a generated one. Not wired in `inline` mode (no visible input to label). Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`) and you set no `id` here.",
199
199
  "required": false,
200
200
  "inherited": false,
201
201
  "deprecated": false
@@ -33,7 +33,7 @@ Bulma forms are a three-tier structure. bestax models it directly:
33
33
 
34
34
  ```
35
35
  Field // container + layout (horizontal / grouped / hasAddons)
36
- ├── label // rendered from Field's `label` prop, or <Field.Label> when horizontal
36
+ ├── label // rendered from Field's `label` prop, or a <label> you put in <Field.Label>
37
37
  └── Control // wraps ONE input; adds icons + loading
38
38
  ├── InputBase / SelectBase / TextAreaBase // the raw styled element
39
39
  └── <p class="help">…</p> // help / validation message
@@ -125,26 +125,41 @@ Full-width is `isFullwidth` on every component that supports it (`Button`, `Link
125
125
  spellings compile only where they historically existed: `isFullWidth` everywhere except
126
126
  `Sidebar`, `fullwidth` on `Tabs` only, `fullWidth` on `Sidebar` only.
127
127
 
128
- The `label` prop on the single-control convenience inputs (`Input`, `Select`, `TextArea`,
129
- `File`, `Numberinput`, `Slider`, `DateInput`, `TimeInput`, `DateTimeInput`, `Autocomplete`,
130
- `Taginput`) wires `htmlFor`/`id` automatically — your `id` is used when provided, a
131
- generated one otherwise, and an explicit `labelProps={{ htmlFor }}` wins. The wiring only
132
- happens when the component renders its own `Field` (nested inside one, the `label` prop is
133
- dropped); the date/time pickers skip it in `inline` mode and `Taginput` skips it at
134
- `maxTags` (no visible input to label). The group inputs (`Checkboxes`, `Radios`, `Rate`,
135
- `DateRangeInput`) associate their `label` too, but group-style: the wrapper gets
136
- `role="group"`/`"radiogroup"` and `aria-labelledby` pointing at the label. Composing yourself
137
- also associates: a labeled `Field` names the one control it holds, whether a composed
138
- `InputBase`/`SelectBase`/`TextAreaBase` or any input above (through the id), or a group
139
- (through `aria-labelledby`). Either way, an `aria-label` or `aria-labelledby` you give a
140
- group wins over the label. A `Checkbox`, `Radio` or `Switch` takes nothing from a `Field`:
141
- each is named by its own children, so put the text there. The association is skipped for
142
- `grouped`/`hasAddons`, and a nested `Field` starts its own scope, so a
143
- horizontal `Field` whose body holds an inner `Field` needs `labelProps={{ htmlFor }}` plus the
144
- control's `id` (for a group, `labelProps={{ id, htmlFor: undefined }}` plus its
145
- `aria-labelledby`, since nothing takes the label's `for` there). Pass
146
- `labelProps={{ htmlFor }}` plus a matching `id` only when you want a stable id, or
147
- `labelProps={{ htmlFor: undefined }}` to opt out.
128
+ **Labels.** Default to the convenience `label` prop, with `horizontal` for a side-by-side row.
129
+ `Input`, `Select`, `TextArea`, `File`, `Numberinput`, `Slider`, `DateInput`, `TimeInput`,
130
+ `DateTimeInput`, `Autocomplete` and `Taginput` then render the `Field` and the label and wire
131
+ `htmlFor`/`id`, to your `id` when you pass one (`<Input label="Email" id="email" horizontal />`);
132
+ an explicit `labelProps={{ htmlFor }}` wins. The group inputs (`Checkboxes`, `Radios`, `Rate`,
133
+ `DateRangeInput`) name their `role="group"`/`"radiogroup"` wrapper through `aria-labelledby`
134
+ instead. Inside a `Field` a convenience input drops its `label`, and an `inline` date/time picker
135
+ or a `Taginput` at `maxTags` has no visible input to wire.
136
+
137
+ Composed, a labeled `Field` names the one control it holds: a composed `InputBase`/`SelectBase`/
138
+ `TextAreaBase`, a composed `DateInputBase`/`TimeInputBase`/`DateTimeInputBase` that is not
139
+ `inline`, or any input above takes its generated id, and a group (a composed `DateRangeInputBase`
140
+ too, `inline` or not) points `aria-labelledby` at the label's own id. **A control with an `id` of
141
+ its own never takes that id**, so `<Field label="Email"><Input id="email" /></Field>` renders a
142
+ label that names nothing: leave the `id` off, or pass the same value as
143
+ `labelProps={{ htmlFor: 'email' }}`. Wire it that way too where the association is skipped, in a
144
+ `grouped`/`hasAddons` row and in a horizontal `Field` whose body holds an inner `Field` (a nested
145
+ `Field` starts its own scope). For a group there, put `labelProps={{ id, htmlFor: undefined }}` on
146
+ the outer `Field` and a matching `aria-labelledby` on the group; the `htmlFor: undefined` keeps an
147
+ unmatched `for` out of server-rendered HTML.
148
+
149
+ A `range` `Slider` points the `for` at its low thumb and starts both thumbs' names with the label
150
+ ("Price range Minimum value"); its `ariaLabel={[low, high]}` replaces those names outright, so
151
+ leave it off when a label already names the Slider. Those thumbs and an `Autocomplete`'s
152
+ suggestion list point `aria-labelledby` at the label's `id`, which a label wired by hand has only
153
+ when you pass one, `labelProps={{ htmlFor, id }}`; without it they keep their fallback names
154
+ ("Minimum value"/"Maximum value", "Suggestions"). A `<label htmlFor>` you put in `Field.Label`
155
+ yourself (`references/patterns.md`) names its control through the `for` alone, so label a row
156
+ holding either with the `Field`'s `label` prop. An `aria-label` or `aria-labelledby` you give a
157
+ group wins over any label, and a `Checkbox`, `Radio` or `Switch` takes nothing from a `Field` on
158
+ its own: its children name it, and a `Field`'s label names one only when you wire it as you would
159
+ any control with an `id` of its own, `labelProps={{ htmlFor }}` plus that control's `id`. A
160
+ labeled `Field` whose `for` nothing takes (a group, a `Checkbox`, `Radio` or `Switch`, an input
161
+ with an `id` of its own, your own markup) drops it once mounted, though server-rendered HTML keeps
162
+ it. `labelProps={{ htmlFor: undefined }}` opts out of the association entirely.
148
163
 
149
164
  ## Convenience vs composed
150
165
 
@@ -261,11 +276,12 @@ for the expected classes/states, and say plainly that the visual pass is still o
261
276
  ## Checklist
262
277
 
263
278
  - [ ] Built from the shipped form components (no hand-rolled inputs / reinvented controls).
264
- - [ ] Every label is programmatically associated — the convenience `label` prop, the group
265
- inputs, and `Field` + single-base composition all do this automatically; pass
266
- `labelProps={{ htmlFor }}` plus a matching `id` only for a stable id, and label a
267
- multi-control `Field`'s controls individually (`aria-label`, `aria-labelledby`, or
268
- a `<label htmlFor>` matching each control's `id`).
279
+ - [ ] Every label is programmatically associated: the convenience `label` prop, the group
280
+ inputs, and `Field` + single-control composition do it on their own. A composed labeled
281
+ `Field` names a control with an `id` of its own, or one in an inner `Field`, only when its
282
+ `labelProps={{ htmlFor }}` matches that `id`, and a multi-control `Field`'s controls are
283
+ labeled individually (`aria-label`, `aria-labelledby`, or a `<label htmlFor>` matching
284
+ each control's `id`).
269
285
  - [ ] Controlled inputs have both `value` and `onChange` (or use `defaultValue` uncontrolled).
270
286
  - [ ] Error state shows via `color="danger"` + `message` + `messageColor="danger"`.
271
287
  - [ ] Grouped/addon layouts use explicit `Field` + `Control` composition.
@@ -8,16 +8,18 @@ via `useBulmaClasses`.
8
8
 
9
9
  Container and layout. Compound parts: `Field.Label`, `Field.Body`.
10
10
 
11
- | Prop | Type | Notes |
12
- | ----------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13
- | `horizontal` | `boolean` | Label + control side by side. Auto-wraps children in `Field.Body`. |
14
- | `grouped` | `boolean \| 'centered' \| 'right' \| 'multiline'` | Controls in a row. |
15
- | `hasAddons` | `boolean \| 'centered' \| 'right'` | Attached controls. |
16
- | `narrow` | `boolean` | Constrain to content width (inside horizontal bodies). |
17
- | `label` | `ReactNode` | Names the one control it holds: a composed base or convenience input through its id, a group through `aria-labelledby` (skipped for `grouped`/`hasAddons`; a nested `Field` starts its own scope). |
18
- | `labelSize` | `'small' \| 'normal' \| 'medium' \| 'large'` | Label size. |
19
- | `labelProps` | label attributes | Props for the `<label>`. An explicit `htmlFor` (even `undefined`) takes over the association; otherwise it is set automatically. An `id` here is what a group's `aria-labelledby` points at. |
20
- | `textColor` / `bgColor` | Bulma color | Helper colors for the field. |
11
+ | Prop | Type | Notes |
12
+ | ----------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13
+ | `horizontal` | `boolean` | Label + control side by side. Auto-wraps children in `Field.Body`. |
14
+ | `grouped` | `boolean \| 'centered' \| 'right' \| 'multiline'` | Controls in a row. |
15
+ | `hasAddons` | `boolean \| 'centered' \| 'right'` | Attached controls. |
16
+ | `narrow` | `boolean` | Constrain to content width (inside horizontal bodies). |
17
+ | `label` | `ReactNode` | Names the one control it holds: a composed base or convenience input with no `id` of its own through a generated id, a group through `aria-labelledby` (skipped for `grouped`/`hasAddons`; a nested `Field` starts its own scope). |
18
+ | `labelSize` | `'small' \| 'normal' \| 'medium' \| 'large'` | Label size. |
19
+ | `labelProps` | label attributes | Props for the `<label>`. An explicit `htmlFor` (even `undefined`) takes over the association; otherwise it is set automatically. For its `id`, see below. |
20
+ | `textColor` / `bgColor` | Bulma color | Helper colors for the field. |
21
+
22
+ An `id` in `labelProps` is what a group's, a range `Slider`'s and an `Autocomplete` suggestion list's `aria-labelledby` points at. A label wired by hand with `htmlFor` gets no generated id, so give it one, `labelProps={{ htmlFor, id }}`, for a range `Slider` or an `Autocomplete`. No group points at a label wired by hand.
21
23
 
22
24
  ## Control — `form/Control.tsx`
23
25
 
@@ -164,18 +164,30 @@ Recipes built on addons, each written out in full in the docs:
164
164
  - **Split button** ([docs](https://bestax.io/docs/api/components/dropdown#split-button)): a
165
165
  `Button` and a `Dropdown` in `Field hasAddons`, one `Control` each. `Buttons hasAddons` does
166
166
  not join the Dropdown's trigger. Keep the default color, since the trigger takes no `color`.
167
- - To label an addon row, put the label on an outer `Field` with `labelProps={{ htmlFor }}` and a
168
- matching `id` on the input. A label inside the `hasAddons` field sits in the row beside the
169
- controls.
167
+ - To label an addon row, put the label on an outer `Field` with `labelProps={{ htmlFor, id }}`
168
+ and a matching `id` on the input. The label's `id` names an `Autocomplete`'s suggestion list
169
+ or a range `Slider`'s thumbs, which point `aria-labelledby` at it. A label inside the
170
+ `hasAddons` field sits in the row beside the controls.
170
171
 
171
172
  ## Horizontal field with explicit label/body
172
173
 
174
+ `Field.Label` is the label column, not a `<label>`, so text placed straight in it names nothing:
175
+ put a `<label className="label">` inside it with `htmlFor` pointing at the input's `id`.
176
+
173
177
  ```tsx
174
178
  <Field horizontal>
175
- <Field.Label size="normal">Email</Field.Label>
179
+ <Field.Label size="normal">
180
+ <label className="label" htmlFor="horizontal-email">
181
+ Email
182
+ </label>
183
+ </Field.Label>
176
184
  <Field.Body>
177
185
  <Control iconLeftName="envelope" hasIconsLeft>
178
- <InputBase type="email" placeholder="you@example.com" />
186
+ <InputBase
187
+ id="horizontal-email"
188
+ type="email"
189
+ placeholder="you@example.com"
190
+ />
179
191
  </Control>
180
192
  </Field.Body>
181
193
  </Field>
@@ -203,7 +215,15 @@ const [volume, setVolume] = useState(50);
203
215
  <Slider value={volume} onChange={setVolume} min={0} max={100} tooltip="auto" />;
204
216
 
205
217
  const [range, setRange] = useState<[number, number]>([20, 80]);
206
- <Slider range value={range} onChange={setRange} min={0} max={100} />;
218
+ // The label names both thumbs: "Price range Minimum value" / "... Maximum value".
219
+ <Slider
220
+ range
221
+ label="Price range"
222
+ value={range}
223
+ onChange={setRange}
224
+ min={0}
225
+ max={100}
226
+ />;
207
227
 
208
228
  // Numberinput
209
229
  const [qty, setQty] = useState(1);
@@ -46,6 +46,12 @@ or `library` silently renders nothing.
46
46
  first thing to check. A redundant `fa-`/`mdi-` prefix in `name` is tolerated (stripped), but
47
47
  don't rely on it.
48
48
 
49
+ **Material Symbols' font is several megabytes however few icons the app shows.** Keep the
50
+ default `material-symbols/outlined.css` import. When the user cares about download size,
51
+ offer a smaller font as an optimisation (a Google Fonts `icon_names` subset, a self-hosted
52
+ subset, or a single-weight package): `references/icon-libraries.md` has each one and its
53
+ trade-offs.
54
+
49
55
  ## Styling
50
56
 
51
57
  - `size` — `'small' | 'medium' | 'large'` sizes the Bulma **container** (`is-small` ≈ 1rem,
@@ -105,6 +111,7 @@ everything else takes the standard `aria-label` attribute, e.g.
105
111
  ## References
106
112
 
107
113
  - `references/icon-libraries.md` — per-library setup (install/import/CDN), the full
108
- name-format and variant tables, `features` values, and the blank-icon troubleshooting list.
114
+ name-format and variant tables, `features` values, making the Material Symbols font
115
+ smaller, and the blank-icon troubleshooting list.
109
116
  - `examples/icon-usage.tsx` — runnable example: ConfigProvider setup, sizes, variants,
110
117
  colors, IconText, and decorative-vs-labeled patterns.
@@ -67,8 +67,11 @@ Set it once on `ConfigProvider` and omit `library` everywhere else.
67
67
 
68
68
  ## Google Material Icons — `'material-icons'`
69
69
 
70
- - **Setup:** `npm install material-icons` and `import 'material-icons';`.
71
- - **Scaffold flag:** `--icon material-icons`.
70
+ - **Setup:** `npm install material-icons` and `import 'material-icons/iconfont/filled.css';`, the
71
+ style `Icon` renders by default. Add `outlined.css`, `round.css`, `sharp.css` or `two-tone.css`
72
+ from `material-icons/iconfont/` for those variants; the bare `import 'material-icons';` loads
73
+ all five fonts.
74
+ - **Scaffold flag:** `--icon material-icons` (imports `filled.css` only).
72
75
  - **Names:** snake_case ligature text: `home`, `rocket_launch`, `shopping_cart`. A kebab-case
73
76
  name will not match a ligature and renders as raw text.
74
77
  - **Variants:** `filled` (default) / `outlined` / `round` / `sharp` — note **`round`**, not
@@ -77,13 +80,48 @@ Set it once on `ConfigProvider` and omit `library` everywhere else.
77
80
 
78
81
  ## Material Symbols — `'material-symbols'`
79
82
 
80
- - **Setup:** `npm install material-symbols` and `import 'material-symbols';`.
81
- - **Scaffold flag:** `--icon material-symbols`.
83
+ - **Setup:** `npm install material-symbols` and `import 'material-symbols/outlined.css';`, the
84
+ style `Icon` renders by default. Add `rounded.css` or `sharp.css` for those variants; the bare
85
+ `import 'material-symbols';` loads all three fonts.
86
+ - **Scaffold flag:** `--icon material-symbols` (imports `outlined.css` only).
82
87
  - **Names:** snake_case ligature text, same as Material Icons: `rocket_launch`.
83
88
  - **Variants:** `outlined` (default) / `rounded` / `sharp` — note **`rounded`** here vs
84
89
  Material Icons' `round`.
85
90
  - **Features:** Bulma classes like `'is-size-1'`.
86
91
 
92
+ ### Making the font smaller
93
+
94
+ `outlined.css` loads the whole variable font (every symbol at every fill, weight, grade and
95
+ optical size), several megabytes however few icons the app shows. Keep that import as the
96
+ default: it is one line, works offline, and any name renders. Offer a smaller font only when
97
+ the user cares about download size (a size budget, a Lighthouse flag, slow networks), and
98
+ present it as an optimisation with the trade-offs below:
99
+
100
+ - **One stylesheet per style rendered.** If the app has the bare `import 'material-symbols';`,
101
+ replace it with `outlined.css`, adding `rounded.css` or `sharp.css` only where a `variant`
102
+ uses them. The bare import ships every style's font in the build (a browser fetches each one
103
+ only when a page renders it). This is where the scaffold already stands, and the style left
104
+ is still the whole variable font, which the options below shrink.
105
+ - **Google Fonts subset.** Replace the import with a stylesheet link in `index.html`:
106
+ `<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined&icon_names=home,palette,settings&display=block" />`.
107
+ `icon_names` takes the ligature names comma-separated and sorted alphabetically, and keep
108
+ `&display=block` (it stops the names flashing as text). The returned CSS defines the same
109
+ `material-symbols-outlined` class, so components don't change. Costs: the browser fetches
110
+ the icons from Google (privacy, no offline use), and the list must hold every name the app
111
+ renders, since a missing one shows as plain text. `variant="rounded"`/`"sharp"` need
112
+ `Material+Symbols+Rounded`/`Material+Symbols+Sharp` requested too, and an app that sets
113
+ `font-variation-settings` must request those axes in the URL.
114
+ - **Self-hosted subset.** Cut a subset from the package's `.woff2` with a font subsetting tool
115
+ (fontTools' subsetter, for one), keeping the glyphs for the names the app renders, and load
116
+ it with the app's own `@font-face` plus the `.material-symbols-outlined` rules. No request to
117
+ Google, same list upkeep.
118
+ - **Fewer variations.** `@material-symbols/font-400`, from the same project, keeps every
119
+ symbol with weight, grade and optical size fixed, at a fraction of the size. Import
120
+ `@material-symbols/font-400/outlined.css` in place of `material-symbols/outlined.css`; it
121
+ defines the same class, and there is no list to maintain.
122
+
123
+ Docs: https://bestax.io/docs/guides/getting-started/alternative-icons#making-the-font-smaller
124
+
87
125
  ## One glyph, five names
88
126
 
89
127
  | Glyph | fa | mdi | ion | material-icons / material-symbols |
@@ -126,6 +126,8 @@ partial inventory, and a worked example are in `references/modular-build.md`.
126
126
  `material-icons`, `material-symbols`) with their CSS imports in `src/main.*`, or Ionicons
127
127
  CDN `<script>` tags in `index.html`. Icon fonts often outweigh Bulma itself — if no
128
128
  `<Icon>` uses that library, delete the dependency/import/script. Pure win, no tooling.
129
+ When the app does render Material Symbols, its font can be made smaller instead of
130
+ removed: see [Making the font smaller](https://bestax.io/docs/guides/getting-started/alternative-icons#making-the-font-smaller).
129
131
 
130
132
  ## References
131
133
 
@@ -81,7 +81,7 @@ is **more complete** than the docs page's Option C example, which omits several)
81
81
  `picker-popover`, `dateinput`, `timeinput`, `datetimeinput`, `daterangeinput`
82
82
 
83
83
  **Elements** (`@allxsmith/bestax-bulma/scss/elements/<name>`): `linkbutton`, `loader`,
84
- `progress`, `skeleton`
84
+ `notification`, `progress`, `skeleton`
85
85
 
86
86
  **Helpers** (`@allxsmith/bestax-bulma/scss/helpers/<name>`): `cursor`, `sizing`
87
87
 
@@ -105,6 +105,10 @@ Notes:
105
105
  `bulma/sass/base/skeleton`). The `progress` and `skeleton` extras partials stop the
106
106
  indeterminate bar's sweep and the skeleton pulse (`Skeleton`, the `skeleton` prop,
107
107
  `hasSkeleton`) under `prefers-reduced-motion: reduce`, the same way and in either order.
108
+ - `Notification` is stock Bulma too (`bulma/sass/elements/notification`). The `notification`
109
+ extras partial pads the end of one with a close button (`hasDelete`, or a `Delete` passed in)
110
+ so its text clears the button, so include it next to the Bulma module in any build that shows
111
+ a dismissible notification. It doesn't depend on the order of the two `@use` lines either.
108
112
  - Stock-Bulma form controls still need their Bulma module (`bulma/sass/form/…`); the extras
109
113
  form partials above style only the bestax-specific behavior.
110
114
  - Bulma component modules pull their own internal sub-elements — e.g.
@@ -153,11 +157,14 @@ not `flexbox` — `flexbox` holds only the alignment props. Verified failure mod
153
157
  ## Worked example — the create-bestax starter app
154
158
 
155
159
  The `npm create bestax` starter renders `Container`, `Section`, `Columns`/`Column`, `Image`,
156
- `Title`/`SubTitle`, `Box`, `Card`, `Buttons`/`Button`, and `Notification` — all stock Bulma,
157
- no bestax extras. Its helper props are `display`/`justifyContent` (visibility + flexbox),
158
- `textAlign` (typography), and `textColor` (color); it uses no spacing props. That inventory
159
- compiles to the following — verified pixel-equivalent to the prebuilt `complete` flavor in
160
- headless Chromium (light and dark), at 453 KB raw / 40 KB gzip vs 813 KB / 83 KB:
160
+ `Title`/`SubTitle`, `Box`, `Card`, `Buttons`/`Button`, and `Notification` with its close
161
+ button: all stock Bulma, plus the `notification` extras partial for that close button. Its
162
+ helper props are `display` (visibility), `flexGrow` (flexbox), and `textAlign` (typography);
163
+ it uses no spacing or color props. That inventory compiles to the following, verified
164
+ pixel-equivalent to the prebuilt `complete` flavor in headless Chromium (light and dark, phone
165
+ and desktop width, notification open), at 274 KB raw / 30 KB gzip vs 819 KB / 84 KB.
166
+ Scaffolded with an icon library, the starter also needs `bulma/sass/elements/icon` for its
167
+ `IconText` card titles:
161
168
 
162
169
  ```scss
163
170
  // src/styles.scss
@@ -170,6 +177,7 @@ headless Chromium (light and dark), at 453 KB raw / 40 KB gzip vs 813 KB / 83 KB
170
177
 
171
178
  @use 'bulma/sass/elements/box';
172
179
  @use 'bulma/sass/elements/button'; // Button + Buttons
180
+ @use 'bulma/sass/elements/delete'; // Notification's close button
173
181
  @use 'bulma/sass/elements/image';
174
182
  @use 'bulma/sass/elements/notification';
175
183
  @use 'bulma/sass/elements/title'; // Title + SubTitle
@@ -179,9 +187,10 @@ headless Chromium (light and dark), at 453 KB raw / 40 KB gzip vs 813 KB / 83 KB
179
187
  @use 'bulma/sass/layout/section';
180
188
 
181
189
  @use 'bulma/sass/helpers/visibility'; // display="flex"
182
- @use 'bulma/sass/helpers/flexbox'; // justifyContent/flexGrow/flexShrink
190
+ @use 'bulma/sass/helpers/flexbox'; // flexGrow
183
191
  @use 'bulma/sass/helpers/typography'; // textAlign
184
- @use 'bulma/sass/helpers/color'; // textColor
192
+
193
+ @use '@allxsmith/bestax-bulma/scss/elements/notification'; // text clears the close button
185
194
  ```
186
195
 
187
196
  ```tsx
@@ -20,7 +20,10 @@ Choose an override path:
20
20
  - **`Theme` component (runtime, preferred).** Exported from the package. Pass named HSL props
21
21
  (`primaryH`, `primaryS`, `primaryL`, …) and/or `bulmaVars={{ '--bulma-*': '…' }}` for everything
22
22
  else. Add `isRoot` to inject the variables globally at `:root` (once, at the app root); omit it to
23
- scope the variables to the wrapped subtree.
23
+ scope the variables to the wrapped subtree. A `Theme` only reaches variables the component
24
+ inherits: Bulma declares component variables such as `--bulma-card-radius` on the component's
25
+ own element, so a `Theme` value for one never applies (it warns in development). Set those on
26
+ the component with `className` or `style` instead (see `references/css-variables.md`).
24
27
  - **Plain CSS.** Set `:root { --bulma-primary-h: …; }` (or any selector) directly.
25
28
  - **Build-time Sass.** `@use 'bulma/sass' with ($primary: #1e6b99)` when compiling Bulma's Sass.
26
29
 
@@ -13,7 +13,9 @@ theming means overriding the right `--bulma-*` values.
13
13
  - `isRoot` → injects the variables globally at `:root` (use once, at the app root).
14
14
  - without `isRoot` → wraps children in a `<div>` and scopes the variables to that subtree.
15
15
  - Named props set the color/scheme HSL channels and common values (see "Theme props" below).
16
- - `bulmaVars` sets any other variable, keyed by its real `--bulma-*` name:
16
+ - `bulmaVars` sets any other variable, keyed by its real `--bulma-*` name. It only changes
17
+ variables the component inherits, though: see "Component variables" below for the ones Bulma
18
+ declares on the component itself.
17
19
 
18
20
  ```tsx
19
21
  <Theme
@@ -101,6 +103,29 @@ and numeric shades `--bulma-<c>-00` … `--bulma-<c>-95`.
101
103
  | `--bulma-size-small` / `-normal` / `-medium` / `-large` | 0.75 / 1 / 1.25 / 1.5rem | via `bulmaVars` |
102
104
  | `--bulma-weight-light/normal/medium/semibold/bold/extrabold` | 300 / 400 / 500 / 600 / 700 / 800 | via `bulmaVars` |
103
105
 
106
+ ## Component variables: set them on the component, not on `Theme`
107
+
108
+ A custom property set on an ancestor only reaches an element that declares no value of its own.
109
+ Bulma declares the scheme, color, typography, radius and spacing tokens on `:root`, so `Theme`
110
+ (scoped or `isRoot`) and plain `:root` CSS both work for them. Some of those Bulma also declares
111
+ again on one component, which keeps its own value while everything else takes yours; that
112
+ component's API page lists the variable as declared on it, and `Theme` doesn't warn about these.
113
+ To change that one component too, set the variable on it as described below.
114
+
115
+ Bulma's per-component variables (`--bulma-card-radius`, `--bulma-tag-h`,
116
+ `--bulma-delete-dimensions`, …) are declared on the component's own element instead (`.card`,
117
+ `.tag`, `.delete`), so a value set on any ancestor, including `Theme`'s `bulmaVars`, always loses
118
+ and nothing changes. `bulmaVars` still accepts those keys, and `Theme` logs a development warning
119
+ naming them.
120
+
121
+ Set a component variable on the component itself: in your CSS on its own class, loaded after
122
+ the library styles (mind the class prefix), e.g. `.card { --bulma-card-radius: 1.5rem; }`, on a
123
+ class you pass through `className`, or with the component's `style` prop. A size or color
124
+ modifier can declare the variable again on a more specific selector (`.delete.is-small`), which a
125
+ lone class loses to and `style` does not. Each component's API page lists its variables under
126
+ **CSS & Sass Variables** and says where Bulma declares them; the extras below follow the same
127
+ rule.
128
+
104
129
  ## Shadow — override the upstream token, not the derived one
105
130
 
106
131
  `--bulma-shadow` (plus its `--bulma-shadow-h`/`-s`/`-l` channels) is a real Bulma variable that
@@ -295,6 +320,14 @@ Declared on the compound `.button.link-button` — see the compound-selector exc
295
320
  `--bulma-loading-overlay-opacity-opaque`, `--bulma-loading-text-color`,
296
321
  `--bulma-loading-text-size`
297
322
 
323
+ ### Notification
324
+
325
+ `--bulma-notification-delete-padding-inline-end` is the end padding of a notification with a close
326
+ button, which keeps the text clear of the button. It applies with `hasDelete`, and to a `Delete`
327
+ passed as a direct child in browsers that support `:has()`. It is the padding for a default-size
328
+ button: a `Delete` passed in with a `size` gets it plus or minus the difference between the two
329
+ sizes. Bulma's own `--bulma-notification-*` variables set the rest.
330
+
298
331
  ### Numberinput
299
332
 
300
333
  `--bulma-numberinput-disabled-opacity`, `--bulma-numberinput-rounded-button-size`,
package/data/skills.json CHANGED
@@ -29,8 +29,8 @@
29
29
  "promptName": "form",
30
30
  "dir": "bestax-form",
31
31
  "references": [
32
- { "id": "api", "file": "references/api.md", "bytes": 12364 },
33
- { "id": "patterns", "file": "references/patterns.md", "bytes": 6943 }
32
+ { "id": "api", "file": "references/api.md", "bytes": 13008 },
33
+ { "id": "patterns", "file": "references/patterns.md", "bytes": 7509 }
34
34
  ],
35
35
  "examples": []
36
36
  },
@@ -43,7 +43,7 @@
43
43
  {
44
44
  "id": "icon-libraries",
45
45
  "file": "references/icon-libraries.md",
46
- "bytes": 6229
46
+ "bytes": 9172
47
47
  }
48
48
  ],
49
49
  "examples": [
@@ -162,7 +162,7 @@
162
162
  {
163
163
  "id": "modular-build",
164
164
  "file": "references/modular-build.md",
165
- "bytes": 9431
165
+ "bytes": 10113
166
166
  }
167
167
  ],
168
168
  "examples": []
@@ -176,7 +176,7 @@
176
176
  {
177
177
  "id": "css-variables",
178
178
  "file": "references/css-variables.md",
179
- "bytes": 29396
179
+ "bytes": 31590
180
180
  },
181
181
  {
182
182
  "id": "themeable-components",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bestax-mcp",
3
- "version": "1.14.3",
3
+ "version": "1.14.4",
4
4
  "mcpName": "io.github.allxsmith/bestax-mcp",
5
5
  "description": "MCP server for @allxsmith/bestax-bulma — component props, examples, CSS variables and agent skills, served offline from a version-pinned index",
6
6
  "type": "module",
@@ -42,14 +42,14 @@
42
42
  "dependencies": {
43
43
  "@modelcontextprotocol/sdk": "^1.31.0",
44
44
  "zod": "^4.4.3",
45
- "@allxsmith/bestax-bulma": "^5.27.3"
45
+ "@allxsmith/bestax-bulma": "^5.27.8"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@jest/globals": "^30.0.0",
49
49
  "@types/jest": "^30.0.0",
50
- "@types/node": "^26.3.0",
50
+ "@types/node": "^26.6.4",
51
51
  "jest": "^30.2.0",
52
- "ts-jest": "^29.4.12",
52
+ "ts-jest": "^29.4.14",
53
53
  "typescript": "^6.0.3"
54
54
  },
55
55
  "publishConfig": {