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 +3 -2
- package/data/components/Autocomplete.json +5 -5
- package/data/components/DateInput.json +1 -1
- package/data/components/DateRangeInput.json +1 -1
- package/data/components/DateTimeInput.json +1 -1
- package/data/components/Delete.json +4 -4
- package/data/components/Field.json +5 -5
- package/data/components/File.json +1 -1
- package/data/components/Input.json +7 -7
- package/data/components/Notification.json +7 -1
- package/data/components/Numberinput.json +1 -1
- package/data/components/Select.json +5 -5
- package/data/components/Slider.json +12 -4
- package/data/components/Taginput.json +1 -1
- package/data/components/TextArea.json +4 -4
- package/data/components/Theme.json +2 -2
- package/data/components/TimeInput.json +1 -1
- package/data/skills/bestax-form/SKILL.md +42 -26
- package/data/skills/bestax-form/references/api.md +12 -10
- package/data/skills/bestax-form/references/patterns.md +26 -6
- package/data/skills/bestax-icons/SKILL.md +8 -1
- package/data/skills/bestax-icons/references/icon-libraries.md +42 -4
- package/data/skills/bestax-optimize/SKILL.md +2 -0
- package/data/skills/bestax-optimize/references/modular-build.md +17 -8
- package/data/skills/bestax-theming/SKILL.md +4 -1
- package/data/skills/bestax-theming/references/css-variables.md +34 -1
- package/data/skills.json +5 -5
- package/package.json +4 -4
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.
|
|
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":
|
|
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
|
|
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={{
|
|
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
|
|
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
|
|
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
|
|
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": "
|
|
132
|
+
"scope": "compound"
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
135
|
"css": "--bulma-delete-background-l",
|
|
136
136
|
"sass": null,
|
|
137
137
|
"default": "0%",
|
|
138
|
-
"scope": "
|
|
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": "
|
|
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": "
|
|
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
|
|
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 `
|
|
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 `
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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>
|
|
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
|
-
|
|
129
|
-
`
|
|
130
|
-
`
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
`
|
|
135
|
-
`
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
`
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
`
|
|
146
|
-
|
|
147
|
-
`
|
|
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
|
|
265
|
-
inputs, and `Field` + single-
|
|
266
|
-
`
|
|
267
|
-
multi-control `Field`'s controls
|
|
268
|
-
a `<label htmlFor>` matching
|
|
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
|
|
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.
|
|
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 }}`
|
|
168
|
-
matching `id` on the input.
|
|
169
|
-
|
|
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">
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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`
|
|
157
|
-
|
|
158
|
-
`
|
|
159
|
-
|
|
160
|
-
headless Chromium (light and dark
|
|
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'; //
|
|
190
|
+
@use 'bulma/sass/helpers/flexbox'; // flexGrow
|
|
183
191
|
@use 'bulma/sass/helpers/typography'; // textAlign
|
|
184
|
-
|
|
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":
|
|
33
|
-
{ "id": "patterns", "file": "references/patterns.md", "bytes":
|
|
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":
|
|
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":
|
|
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":
|
|
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
|
+
"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.
|
|
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.
|
|
50
|
+
"@types/node": "^26.6.4",
|
|
51
51
|
"jest": "^30.2.0",
|
|
52
|
-
"ts-jest": "^29.4.
|
|
52
|
+
"ts-jest": "^29.4.14",
|
|
53
53
|
"typescript": "^6.0.3"
|
|
54
54
|
},
|
|
55
55
|
"publishConfig": {
|