create-bestax 4.0.0 → 4.0.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,SAAS,EAAE,QAAQ,EAG/B,CAAC;AAEF,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AACpD,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,kBAAkB,QAAsB,CAAC;AAEtD,eAAO,MAAM,QAAQ;;;;;;;wCAaQ,MAAM;uCAEP,MAAM;sCACP,MAAM;4CAEA,MAAM;;;;;CAM7B,CAAC;AASX,eAAO,MAAM,WAAW,QAed,CAAC;AAEX,eAAO,MAAM,OAAO;;;;;;CAOV,CAAC;AAOX,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAK9D,CAAC;AAKF,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,SAAS,GACpB,aAAa,MAAM,EACnB,8BAA8B,eAAe,KAC5C,MAiHF,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,cAAc,EAAE,WAAW,EA6CvC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,aAAa,EAAE,WAAW,EA4CtC,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,SAAS,EAAE,QAAQ,EAG/B,CAAC;AAEF,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AACpD,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,kBAAkB,QAAsB,CAAC;AAEtD,eAAO,MAAM,QAAQ;;;;;;;wCAaQ,MAAM;uCAEP,MAAM;sCACP,MAAM;4CAEA,MAAM;;;;;CAM7B,CAAC;AASX,eAAO,MAAM,WAAW,QAed,CAAC;AAEX,eAAO,MAAM,OAAO;;;;;;CAOV,CAAC;AAOX,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAK9D,CAAC;AAKF,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,SAAS,GACpB,aAAa,MAAM,EACnB,8BAA8B,eAAe,KAC5C,MAkIF,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,cAAc,EAAE,WAAW,EA6CvC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,aAAa,EAAE,WAAW,EA4CtC,CAAC"}
package/dist/constants.js CHANGED
@@ -129,6 +129,7 @@ cases. Before writing \`style\`, translate each declaration with this table:
129
129
  and \`Tabs.Tab\` has built-in \`icon\`/\`disabled\` props — no nested \`Icon\` needed.
130
130
  - Compose existing components before writing custom CSS; theme via \`Theme\` and \`--bulma-*\`
131
131
  variables, never hardcoded colors.
132
+
132
133
  - \`Navbar.Burger\`/\`Navbar.Menu\` are controlled — wire \`active\` via state on both, and pair
133
134
  \`Navbar fixed="top"\` with the \`has-navbar-fixed-top\` class on \`<html>\` (never an inline
134
135
  padding offset).
@@ -144,6 +145,22 @@ cases. Before writing \`style\`, translate each declaration with this table:
144
145
  (\`pnpm-lock.yaml\` → pnpm, \`package-lock.json\` → npm, \`yarn.lock\` → yarn) — a mismatched
145
146
  install fails or forks the lockfile.
146
147
 
148
+ ### Three components Bulma will talk you out of
149
+
150
+ Everything else here gets found because Bulma has no equivalent; these three have a near-miss
151
+ close enough to end the search.
152
+
153
+ - **Confirmation after an action** — \`Toast\`, not \`Notification\`/\`Message\`. Mount
154
+ \`<ToastContainer position="top-right" />\` once at the root, then \`toast.success('Saved')\`.
155
+ - **A confirm or alert** — \`Dialog\`, not \`Modal\`. Mount \`<DialogContainer />\`, then
156
+ \`if (await dialog.confirm({ title, message })) …\`.
157
+ - **A control that reads as text or a link** — \`LinkButton\`
158
+ (\`variant="text" \\| "ghost" \\| "underline"\`), not \`<a href="#">\`, \`<div onClick>\` or
159
+ \`Button color="text"\`.
160
+
161
+ Mounting a container without ever calling \`toast.*\`/\`dialog.*\` does nothing. Both also work
162
+ as controlled components (\`<Toast message … onClose>\`, \`<Dialog isOpen … onConfirm>\`).
163
+
147
164
  ## AI skills
148
165
 
149
166
  \`.claude/skills/\` contains Agent Skills that teach Claude how to build with this library. They load
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bestax",
3
- "version": "4.0.0",
3
+ "version": "4.0.2",
4
4
  "description": "Create a new bestax-bulma project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -100,18 +100,18 @@ All import from `@allxsmith/bestax-bulma`. Convenience components auto-wrap Fiel
100
100
 
101
101
  Across the convenience inputs (`Input`, `Select`, `TextArea`, and similar):
102
102
 
103
- | Prop | Type | Purpose |
104
- | -------------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------- |
105
- | `color` | `'primary' \| 'link' \| 'info' \| 'success' \| 'warning' \| 'danger'` | Visual state — use `'danger'` for errors, `'success'` for valid. |
106
- | `size` | `'small' \| 'medium' \| 'large'` | Input size. |
107
- | `value` / `onChange` | controlled value + handler | Standard React controlled inputs. |
108
- | `defaultValue` | uncontrolled initial value | When not controlling state. |
109
- | `disabled`, `readOnly` | `boolean` | Native states (`readOnly` on `*Base`). |
110
- | `label` | `ReactNode` | Field label (convenience components). |
111
- | `message` | `ReactNode` | Help / validation text rendered as `<p class="help">`. |
112
- | `messageColor` | a Bulma color | Colors the help text (`'danger'` for errors). |
113
- | `iconLeftName` / `iconRightName` | `string` | Icon shortcuts; pair with `hasIconsLeft/Right`. |
114
- | `isLoading` | `boolean` | Loading indicator on the Control. |
103
+ | Prop | Type | Purpose |
104
+ | -------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------- |
105
+ | `color` | `'primary' \| 'link' \| 'info' \| 'success' \| 'warning' \| 'danger'` | Visual state — use `'danger'` for errors, `'success'` for valid. |
106
+ | `size` | `'small' \| 'medium' \| 'large'` | Input size. |
107
+ | `value` / `onChange` | controlled value + handler | Standard React controlled inputs. |
108
+ | `defaultValue` | uncontrolled initial value | When not controlling state. |
109
+ | `disabled`, `readOnly` | `boolean` | Native states (`readOnly` on `*Base`). |
110
+ | `label` | `ReactNode` | Field label (convenience components; auto-associated via `htmlFor`). |
111
+ | `message` | `ReactNode` | Help / validation text rendered as `<p class="help">`. |
112
+ | `messageColor` | a Bulma color | Colors the help text (`'danger'` for errors). |
113
+ | `iconLeftName` / `iconRightName` | `string` | Icon shortcuts; pair with `hasIconsLeft/Right`. |
114
+ | `isLoading` | `boolean` | Loading indicator on the Control. |
115
115
 
116
116
  Plus the full Bulma **helper props** (`m`, `p`, `textColor`, `display`, …) on every component
117
117
  via `useBulmaClasses`.
@@ -119,9 +119,19 @@ via `useBulmaClasses`.
119
119
  ⚠️ Full-width casing is inconsistent across the library: `Select`, `File`, and `Table` take
120
120
  `isFullwidth` (lowercase w); `Button` alone takes `isFullWidth`; `Tabs` takes bare `fullwidth`.
121
121
 
122
- ⚠️ The `label` prop renders the `<label>` but does **not** wire `htmlFor`/`id` — assistive tech
123
- gets no association. Pass `id` on the input plus `labelProps={{ htmlFor: sameId }}` (every
124
- convenience input and `Field` accept `labelProps`).
122
+ The `label` prop on the single-control convenience inputs (`Input`, `Select`, `TextArea`,
123
+ `File`, `Numberinput`, `Slider`, `DateInput`, `TimeInput`, `DateTimeInput`, `Autocomplete`,
124
+ `Taginput`) wires `htmlFor`/`id` automatically — your `id` is used when provided, a
125
+ generated one otherwise, and an explicit `labelProps={{ htmlFor }}` wins. The wiring only
126
+ happens when the component renders its own `Field` (nested inside one, the `label` prop is
127
+ dropped); the date/time pickers skip it in `inline` mode and `Taginput` skips it at
128
+ `maxTags` (no visible input to label). The group inputs (`Checkboxes`, `Radios`, `Rate`)
129
+ associate their `label` too, but group-style: the wrapper gets `role="group"`/`"radiogroup"`
130
+ and `aria-labelledby` pointing at the label. Composing `Field` + bases yourself also
131
+ associates: `Field`'s own `label` wires to a single composed `InputBase`/`SelectBase`/
132
+ `TextAreaBase` (skipped for `grouped`/`hasAddons`). Pass `labelProps={{ htmlFor }}` plus a
133
+ matching `id` only when you want a stable id, or `labelProps={{ htmlFor: undefined }}` to
134
+ opt out — e.g. when the labeled `Field` wraps something that is not one of those bases.
125
135
 
126
136
  ## Convenience vs composed
127
137
 
@@ -144,17 +154,16 @@ function SignupForm() {
144
154
  const [email, setEmail] = useState('');
145
155
  const [touched, setTouched] = useState(false);
146
156
 
157
+ const valid = /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email);
147
158
  const error =
148
- touched && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)
149
- ? 'Please enter a valid email address.'
150
- : undefined;
159
+ touched && !valid ? 'Please enter a valid email address.' : undefined;
151
160
 
152
161
  return (
153
162
  <form
154
163
  onSubmit={e => {
155
164
  e.preventDefault();
156
165
  setTouched(true);
157
- if (!error && email) {
166
+ if (valid) {
158
167
  // submit…
159
168
  }
160
169
  }}
@@ -198,6 +207,23 @@ inventory above). **Compose these; don't hand-roll raw `<input class="input">` m
198
207
  reinvent a control.** If you think a control is missing, check `bulma-ui/src/index.ts` and
199
208
  `docs/docs/api/form/` first — it's probably already there under a different name.
200
209
 
210
+ ## What happens after submit
211
+
212
+ A form is not finished at the last field. The two components that carry the result are easy to
213
+ miss because Bulma has something that looks close:
214
+
215
+ - **Confirmation** — `Toast`, not `Notification`/`Message`. Mount
216
+ `<ToastContainer position="top-right" />` once at the app root, then call
217
+ `toast.success('Demo booked')` from the submit handler (`.danger` for a failed submit). It
218
+ self-dismisses; a `Notification` is a static element you place and tear down yourself.
219
+ - **"Are you sure?"** — `Dialog`, not `Modal`. Mount `<DialogContainer />` at the root, then
220
+ `if (await dialog.confirm({ title: 'Delete this key?', message: '…', type: 'danger' })) …`.
221
+ It resolves to a boolean, so a destructive action stays one `if` rather than a state machine.
222
+ `Modal` is an empty shell — with it you rebuild the title, message and button row by hand.
223
+
224
+ Both also work as plain controlled components (`<Toast message … onClose>`,
225
+ `<Dialog isOpen … onConfirm onCancel>`) when the state should live in your component.
226
+
201
227
  ## Visually inspect it in a browser
202
228
 
203
229
  Forms have layout, spacing, and _stateful_ behavior that types and unit tests don't cover.
@@ -212,12 +238,17 @@ for the expected classes/states, and say plainly that the visual pass is still o
212
238
  ## Checklist
213
239
 
214
240
  - [ ] Built from the shipped form components (no hand-rolled inputs / reinvented controls).
215
- - [ ] Every label is programmatically associated: `label` prop + `id` on the input +
216
- `labelProps={{ htmlFor }}`, or a `<label htmlFor>` when composing.
241
+ - [ ] Every label is programmatically associated the convenience `label` prop, the group
242
+ inputs, and `Field` + single-base composition all do this automatically; pass
243
+ `labelProps={{ htmlFor }}` plus a matching `id` only for a stable id, and label a
244
+ multi-control `Field`'s controls individually (`aria-label`, `aria-labelledby`, or
245
+ a `<label htmlFor>` matching each control's `id`).
217
246
  - [ ] Controlled inputs have both `value` and `onChange` (or use `defaultValue` uncontrolled).
218
247
  - [ ] Error state shows via `color="danger"` + `message` + `messageColor="danger"`.
219
248
  - [ ] Grouped/addon layouts use explicit `Field` + `Control` composition.
220
249
  - [ ] No assumption of a built-in validation/form library — state is owned by the app.
250
+ - [ ] Submit feedback is a `Toast` and any "are you sure?" is a `Dialog` — not a hand-placed
251
+ `Notification` or a `Modal` you filled in yourself.
221
252
  - [ ] **Rendered and visually inspected in a browser** — layout and the error/validation states
222
253
  look right, not just green tests. No browser available? The `renderToString` fallback above
223
254
  counts only if you grepped the emitted classes/states **and** said the visual pass is owed.
@@ -8,16 +8,16 @@ 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` | Convenience label. |
18
- | `labelSize` | `'small' \| 'normal' \| 'medium' \| 'large'` | Label size. |
19
- | `labelProps` | label attributes | Props for the `<label>` where `htmlFor` goes. |
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` | Convenience label; auto-associates with a single composed `InputBase`/`SelectBase`/`TextAreaBase` (skipped for `grouped`/`hasAddons`). |
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. |
21
21
 
22
22
  ## Control — `form/Control.tsx`
23
23
 
@@ -70,11 +70,12 @@ Centered; a collection of items → Card grid. For mixed requests, pick the domi
70
70
  `textWeight`, `textSize` directly (their `color` prop colors the cell; for muted cell text
71
71
  wrap content in `Span textColor="grey"`). Set the app-wide icon library once with
72
72
  `<ConfigProvider iconLibrary="…">` at the root rather than `library` on every `<Icon>`.
73
- - **Decorative CSS is budgeted: two compact rules, ≤10 lines per app — comments count:
73
+ - **Decorative CSS is budgeted: three compact rules, ≤13 lines per app — comments count:
74
74
  at most one short inline note, never a file-header comment block — every value derived
75
- from `--bulma-*`.** A marketing page gets at most one hero wash + one alternating section
76
- band, applied via `className` — no resets (Bulma ships one; body/list margins are already
77
- zero) and no grid textures, masks, or multi-layer backdrops; the components carry the design:
75
+ from `--bulma-*`.** A marketing page gets at most one hero wash, one alternating section
76
+ band, and one featured-card ring, applied via `className` — no resets (Bulma ships one;
77
+ body/list margins are already zero) and no grid textures, masks, or multi-layer backdrops;
78
+ the components carry the design:
78
79
 
79
80
  ```css
80
81
  .hero-wash {
@@ -87,14 +88,19 @@ Centered; a collection of items → Card grid. For mixed requests, pick the domi
87
88
  .section-alt {
88
89
  background: var(--bulma-scheme-main-bis); /* next band: -ter */
89
90
  }
91
+ .featured-ring {
92
+ --bulma-shadow: 0 0 0 2px var(--bulma-primary);
93
+ }
90
94
  ```
91
95
 
92
- A highlighted/"featured" `Card`/`Box` needs **no third rule**: wrap that one element in
93
- `<Theme bulmaVars={{ '--bulma-shadow': '0 0 0 2px var(--bulma-primary)' }}>`. Override the
94
- **upstream token**, not the component's own var: `.card`/`.box` re-declare
95
- `--bulma-card-shadow`/`--bulma-box-shadow` on their own selector, so setting those from an
96
- ancestor never wins (same for `--bulma-box-radius`; `--bulma-card-radius` is a literal with
97
- no ancestor route at all). The subtree stays theme- and dark-mode-aware.
96
+ The ring works by overriding the **upstream token**, not the component's own var: `.card`
97
+ and `.box` re-declare `--bulma-card-shadow`/`--bulma-box-shadow` from `--bulma-shadow` on
98
+ their own selector, so setting _those_ from an ancestor never wins (same for
99
+ `--bulma-box-radius`; `--bulma-card-radius` is a literal with no ancestor route at all).
100
+ It has to be a CSS rule rather than `<Theme bulmaVars={{ '--bulma-shadow': }}>`, because
101
+ `bulmaVars` is a closed typed record and `--bulma-shadow` is not one of its keys — that
102
+ exact call **does not compile**, and it was the single most repeated invention across this
103
+ library's cold-start evals. Either way the subtree stays theme- and dark-mode-aware.
98
104
 
99
105
  - **CTAs on a colored hero must stay legible in both schemes.** On a fixed-color surface
100
106
  (`Hero color="primary"`, a dark banner), use **filled** buttons — `color="light"` or
@@ -104,6 +110,26 @@ Centered; a collection of items → Card grid. For mixed requests, pick the domi
104
110
  `<Theme isRoot colorMode="light">` — so a visitor's OS dark mode can't flip Bulma's text
105
111
  colors out from under the fixed palette (details: the `bestax-theming` skill's contrast rules).
106
112
 
113
+ ## Three components core Bulma will talk you out of
114
+
115
+ Most of this library's additions get found on their own, because nothing in Bulma does the
116
+ job. These three do not: each has a Bulma near-miss close enough to stop the search. Across
117
+ 44 cold-start builds, `Dialog` was used **zero** times and `LinkButton` in two thirds — and
118
+ every miss shipped the "not this" column instead.
119
+
120
+ | You need | Use | Not this |
121
+ | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
122
+ | A brief confirmation after an action, self-dismissing | `Toast` — mount `<ToastContainer position="top-right" />` once at the app root, then `toast.success('Saved')` (also `.danger`/`.warning`/`.info`) from anywhere | `Notification`/`Message` — static page elements you have to place, position and dismiss yourself |
123
+ | A confirm or alert the user must answer before anything | `Dialog` — mount `<DialogContainer />` at the root, then `if (await dialog.confirm({ title, message })) …` | `Modal` — an empty shell; the title, message, button row and confirm/cancel wiring are all yours to rebuild |
124
+ | A control that reads as text or a link but _does_ something | `LinkButton` (`variant="text" \| "ghost" \| "underline"`, optional `color`) | `<a href="#">`/`<div onClick>` (no keyboard or screen-reader support) or `Button color="text"` (still button-shaped) |
125
+
126
+ Mounting a container without ever calling `toast.*`/`dialog.*` is not usage — the container is
127
+ the mount point, the imperative call is the thing that shows something. Both also work as
128
+ ordinary controlled components when you would rather hold the state yourself —
129
+ `<Toast message … duration onClose>`, `<Dialog isOpen title message type onConfirm onCancel>` —
130
+ but in an app with more than one call site the root container plus the imperative helper is
131
+ less wiring, not more.
132
+
107
133
  ## Inline style → helper prop mapping
108
134
 
109
135
  Look up the declaration you were about to inline. The spacing, typography, and flex helpers
@@ -157,11 +183,14 @@ inline `style`.
157
183
  - [ ] Wire `active` state to **both** `Navbar.Burger` and `Navbar.Menu` (they are controlled).
158
184
  - [ ] For a fixed navbar, add `has-navbar-fixed-top` to `<html>`.
159
185
  - [ ] Do not use `Tile` — it is not shipped.
186
+ - [ ] Action feedback goes through `Toast`, a confirmation through `Dialog`, a text-styled
187
+ action through `LinkButton` — not `Notification`, `Modal` or a bare `<a>`.
160
188
  - [ ] Style with helper props, not inline `style` — translate via the mapping table; values
161
189
  with no helper get a named class in the stylesheet, never `style={{}}`. No raw Bulma
162
190
  `className`s either (`Span`/`Paragraph` wrap bare text; `Th`/`Td` take `textAlign`/`textWeight`).
163
- - [ ] Decorative CSS ≤10 lines total incl. comments — no file-header comment (hero wash + section band), `--bulma-*`-derived;
164
- no resets Bulma ships one. A featured-card ring is a scoped `<Theme bulmaVars>`, not CSS.
191
+ - [ ] Decorative CSS ≤13 lines total incl. comments — no file-header comment (hero wash +
192
+ section band + featured-card ring), `--bulma-*`-derived; no resets Bulma ships one.
193
+ The ring sets `--bulma-shadow` in a CSS rule; `Theme bulmaVars` has no such key.
165
194
  - [ ] Set the icon library once via `<ConfigProvider iconLibrary="…">` at the root.
166
195
  - [ ] Site built? ~800 KB raw / ~82 KB gzip CSS is the expected default-flavor size — to shrink
167
196
  it, run the `bestax-optimize` skill (measure first).
@@ -58,11 +58,11 @@ Every component also accepts the shared Bulma helper props (`m`/`p` spacing, `te
58
58
 
59
59
  `<Hero>` is a full-width banner. Subcomponents: `Hero.Head`, `Hero.Body`, `Hero.Foot`.
60
60
 
61
- | Prop | Type |
62
- | ---------------------- | ---------------------------------------------------------------------------- |
63
- | `color` | Bulma color |
64
- | `size` | `'small' \| 'medium' \| 'large' \| 'fullheight' \| 'fullheight-with-navbar'` |
65
- | `fullheightWithNavbar` | `boolean` |
61
+ | Prop | Type |
62
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
63
+ | `color` | `primary \| link \| info \| success \| warning \| danger \| black \| white \| light \| dark` (CSS-backed). Also accepted but deprecated, no CSS, dev-warn: `black-bis`, `black-ter`, the greys, `inherit`, `current` |
64
+ | `size` | `'small' \| 'medium' \| 'large' \| 'fullheight' \| 'fullheight-with-navbar'` |
65
+ | `fullheightWithNavbar` | `boolean` |
66
66
 
67
67
  ```tsx
68
68
  <Hero color="primary" size="medium">
@@ -100,6 +100,27 @@ and numeric shades `--bulma-<c>-00` … `--bulma-<c>-95`.
100
100
  | `--bulma-size-small` / `-normal` / `-medium` / `-large` | 0.75 / 1 / 1.25 / 1.5rem | via `bulmaVars` |
101
101
  | `--bulma-weight-light/normal/medium/semibold/bold/extrabold` | 300 / 400 / 500 / 600 / 700 / 800 | via `bulmaVars` |
102
102
 
103
+ ## Shadow — the one token `bulmaVars` cannot set
104
+
105
+ `--bulma-shadow` is a real Bulma variable (`.box` and `.card` both derive
106
+ `--bulma-box-shadow`/`--bulma-card-shadow` from it), but it is **not a key of `bulmaVars`** —
107
+ that record is a closed union built from an explicit list, and shadow was left out. Writing
108
+ `<Theme bulmaVars={{ '--bulma-shadow': … }}>` is a compile error, and it is the single most
109
+ repeated invention in this library's cold-start evals: every run on every guidance channel
110
+ tried it.
111
+
112
+ Set it in CSS instead, then apply the class to the one element that needs it:
113
+
114
+ ```css
115
+ .featured-ring {
116
+ --bulma-shadow: 0 0 0 2px var(--bulma-primary);
117
+ }
118
+ ```
119
+
120
+ Do **not** reach for `--bulma-box-shadow`/`--bulma-card-shadow` from an ancestor instead —
121
+ those are re-declared on `.box`/`.card` themselves, so an inherited value always loses (the
122
+ same rule as the extras section below).
123
+
103
124
  ## Extras component variables
104
125
 
105
126
  Every "Beyond Bulma" extra registers its own `--bulma-<component>-*` variables. They are
@@ -10,9 +10,14 @@ This is the self-contained inventory of the color/size/variant props that matter
10
10
  values are component-specific (see the table). Example: `<Button color="primary">` → `is-primary`.
11
11
  ⚠️ Some unions are **typed wider than the CSS Bulma ships** — the class is emitted but no rule
12
12
  matches. No component ships `is-grey*`/`is-*-bis`/`is-*-ter` rules at all: those `validColors`
13
- members typecheck on `Progress`/`Notification`/`Hero` but style nothing (the `has-text-*`/
14
- `has-background-*` **helpers** do cover all 17). Before relying on an unusual value, grep the
15
- shipped CSS: `node_modules/@allxsmith/bestax-bulma/dist/bestax.css` for e.g. `.progress.is-grey`.
13
+ members still typecheck on `Progress`/`Notification`/`Hero` but style nothing. They are
14
+ **deprecated**: passing one logs a console warning in development, and they will be removed
15
+ from those unions in the next major (the `has-text-*`/`has-background-*` **helpers** do cover
16
+ all 17). On the `Hero` root, `inherit` and `current` are equally CSS-less and deprecated
17
+ (its `Head`/`Body`/`Foot` sub-components keep them, as text helpers). `Pagination` and
18
+ `Tabs` ship no color CSS for **any** value — their entire `color`
19
+ prop is deprecated. Before relying on an unusual value, grep the shipped CSS:
20
+ `node_modules/@allxsmith/bestax-bulma/dist/bestax.css` for e.g. `.progress.is-grey`.
16
21
  2. **Helper color props** (on most components, applied as utility classes):
17
22
  - `color` / `textColor` → `has-text-<color>` (text color)
18
23
  - `backgroundColor` / `bgColor` → `has-background-<color>` (background)
@@ -21,7 +26,8 @@ This is the self-contained inventory of the color/size/variant props that matter
21
26
  Components with a real `is-<color>` modifier (`Button`, `Hero`) drop the `color` helper and
22
27
  re-expose it as **`textColor`** / **`bgColor`**. `Box`/`Card`/`Section` ship no `is-<color>`
23
28
  rule — their `color` _is_ the text helper (`has-text-<color>`; narrowed to the 6 on
24
- `Box`/`Card`), so `color` and `textColor` are the same lever there. `Tag` and `Td`/`Th` have
29
+ `Box`/`Card`), so `color` and `textColor` are the same lever there (prefer `textColor`; it
30
+ takes precedence when both are set). `Tag` and `Td`/`Th` have
25
31
  **no text-color prop** — wrap content in `<Span textColor="…">`. `Input` has none either and
26
32
  the wrapper trick can't work (it renders a native `<input>`; a child can't color its value):
27
33
  recolor via the upstream `--bulma-text-strong-l`, since Bulma re-declares `--bulma-input-*` on
@@ -40,43 +46,43 @@ Shades (`colorShade` / `backgroundColorShade`): `00, 05, 10, … 95, invert, lig
40
46
 
41
47
  ## Component `color` / `size` props (verbatim unions)
42
48
 
43
- | Component | `color` accepts | `size` accepts | Notes |
44
- | ------------------ | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
45
- | `Button` | `primary \| link \| info \| success \| warning \| danger \| white \| light \| dark \| black \| text \| ghost` | `small \| normal \| medium \| large` | adds `text`, `ghost`; also `isLight`, `isOutlined`, `isInverted`, `isRounded` |
46
- | `Notification` | the 17 `validColors` (greys typecheck, no CSS — see ⚠️) | — | also `isLight` |
47
- | `Tag` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `normal \| medium \| large` | also `isRounded`, `isDelete`, `isHoverable` — **no `isLight`** |
48
- | `Box` | `primary \| link \| info \| success \| warning \| danger` | — | `color` renders `has-text-<color>` (no `.box.is-*` ships — tint via `bgColor`); also `hasShadow` |
49
- | `Message` | `primary \| link \| info \| success \| warning \| danger` | — | the 6 only |
50
- | `Input` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isRounded`, `isStatic` |
51
- | `Avatar` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `16x16 \| 24x24 \| 32x32 \| 48x48 \| 64x64 \| 96x96 \| 128x128 \| number` | initials/icon background (auto-derived from `name` when unset); also `shape` |
52
- | `Badge` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | pill background; default `danger` |
53
- | `Title` | — (no `color`; use `textColor`) | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | also `isSpaced` |
54
- | `SubTitle` | — (no `color`; use `textColor`) | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | — |
55
- | `Autocomplete` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | the 6 only |
56
- | `Checkbox` | `primary \| link \| info \| success \| warning \| danger` | `small \| normal \| medium \| large` | the 6 only |
57
- | `DateInput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `isRounded` |
58
- | `DateTimeInput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `isRounded` |
59
- | `File` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isBoxed`, `isFullwidth` |
60
- | `Hero` | the 17 `validColors` (greys typecheck, no CSS — see ⚠️) | `small \| medium \| large \| fullheight \| fullheight-with-navbar` | section background |
61
- | `LinkButton` | `primary \| link \| info \| success \| warning \| danger \| white \| light \| dark \| black` | — | button-styled link; emits `link-button-<color>` — **no `isLight`/`isOutlined`/`isInverted`** |
62
- | `Loading` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | spinner color; default light grey |
63
- | `Navbar` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | — |
64
- | `Numberinput` | `primary \| link \| info \| success \| warning \| danger \| light \| dark` | `small \| medium \| large` | also `inputColor` (the 6) for the inner input |
65
- | `Pagination` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | |
66
- | `Panel` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | — |
67
- | `Progress` | the 17 `validColors` (greys typecheck, no CSS — see ⚠️) | `small \| medium \| large` | — |
68
- | `Radio` | `primary \| link \| info \| success \| warning \| danger` | `small \| normal \| medium \| large` | the 6 only |
69
- | `Rate` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | the 6 only |
70
- | `Select` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isRounded` |
71
- | `Slider` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `isRounded`, `isCircle` |
72
- | `Steps` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | the 6 only |
73
- | `Switch` | `primary \| link \| info \| success \| warning \| danger` | `small \| normal \| medium \| large` | also `isRounded`, `isThin`, `isOutlined` |
74
- | `Tabs` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | |
75
- | `Taginput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `tagColor` (the 6 + `dark \| light`) for the tags |
76
- | `TextArea` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isRounded`, `isStatic` |
77
- | `TimeInput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | — |
78
- | `Tooltip` | `primary \| link \| info \| success \| warning \| danger \| dark \| light` | `small \| medium \| large` | — |
79
- | `Tr` / `Td` / `Th` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | cell background; cells take `textAlign`/`textWeight`/`textSize` directly |
49
+ | Component | `color` accepts | `size` accepts | Notes |
50
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
51
+ | `Button` | `primary \| link \| info \| success \| warning \| danger \| white \| light \| dark \| black \| text \| ghost` | `small \| normal \| medium \| large` | adds `text`, `ghost`; also `isLight`, `isOutlined`, `isInverted`, `isRounded` |
52
+ | `Notification` | the 17 `validColors` (`black-bis`, `black-ter`, and greys deprecated: no CSS, dev-warn, removed next major — see ⚠️) | — | also `isLight` |
53
+ | `Tag` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `normal \| medium \| large` | also `isRounded`, `isDelete`, `isHoverable` — **no `isLight`** |
54
+ | `Box` | `primary \| link \| info \| success \| warning \| danger` | — | `color` renders `has-text-<color>`, same as `textColor` (which wins when both are set; no `.box.is-*` ships — tint via `bgColor`); also `hasShadow` |
55
+ | `Message` | `primary \| link \| info \| success \| warning \| danger` | — | the 6 only |
56
+ | `Input` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isRounded`, `isStatic` |
57
+ | `Avatar` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `16x16 \| 24x24 \| 32x32 \| 48x48 \| 64x64 \| 96x96 \| 128x128 \| number` | initials/icon background (auto-derived from `name` when unset); also `shape` |
58
+ | `Badge` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | pill background; default `danger` |
59
+ | `Title` | — (no `color`; use `textColor`) | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | also `isSpaced` |
60
+ | `SubTitle` | — (no `color`; use `textColor`) | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | — |
61
+ | `Autocomplete` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | the 6 only |
62
+ | `Checkbox` | `primary \| link \| info \| success \| warning \| danger` | `small \| normal \| medium \| large` | the 6 only |
63
+ | `DateInput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `isRounded` |
64
+ | `DateTimeInput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `isRounded` |
65
+ | `File` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isBoxed`, `isFullwidth` |
66
+ | `Hero` | the 17 `validColors` + `inherit`/`current` (`black-bis`, `black-ter`, greys, `inherit`, `current` deprecated: no CSS, dev-warn, removed next major — see ⚠️) | `small \| medium \| large \| fullheight \| fullheight-with-navbar` | section background |
67
+ | `LinkButton` | `primary \| link \| info \| success \| warning \| danger \| white \| light \| dark \| black` | — | button-styled link; emits `link-button-<color>` — **no `isLight`/`isOutlined`/`isInverted`** |
68
+ | `Loading` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | spinner color; default light grey |
69
+ | `Navbar` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | — |
70
+ | `Numberinput` | `primary \| link \| info \| success \| warning \| danger \| light \| dark` | `small \| medium \| large` | also `inputColor` (the 6) for the inner input |
71
+ | `Pagination` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | **entire `color` prop deprecated** (no CSS ships; dev-warn; removal next major) |
72
+ | `Panel` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | — |
73
+ | `Progress` | the 17 `validColors` (`black-bis`, `black-ter`, and greys deprecated: no CSS, dev-warn, removed next major — see ⚠️) | `small \| medium \| large` | — |
74
+ | `Radio` | `primary \| link \| info \| success \| warning \| danger` | `small \| normal \| medium \| large` | the 6 only |
75
+ | `Rate` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | the 6 only |
76
+ | `Select` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isRounded` |
77
+ | `Slider` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `isRounded`, `isCircle` |
78
+ | `Steps` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | the 6 only |
79
+ | `Switch` | `primary \| link \| info \| success \| warning \| danger` | `small \| normal \| medium \| large` | also `isRounded`, `isThin`, `isOutlined` |
80
+ | `Tabs` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | **entire `color` prop deprecated** (no CSS ships; dev-warn; removal next major) |
81
+ | `Taginput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `tagColor` (the 6 + `dark \| light`) for the tags |
82
+ | `TextArea` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isRounded`, `isStatic` |
83
+ | `TimeInput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | — |
84
+ | `Tooltip` | `primary \| link \| info \| success \| warning \| danger \| dark \| light` | `small \| medium \| large` | — |
85
+ | `Tr` / `Td` / `Th` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | cell background; cells take `textAlign`/`textWeight`/`textSize` directly |
80
86
 
81
87
  The 6 brand colors (`primary, link, info, success, warning, danger`) are the ones a custom theme
82
88
  recolors via the HSL trios (see `css-variables.md`). The greyscale and `white`/`light`/`dark`