create-bestax 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,MA2EF,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,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"}
package/dist/constants.js CHANGED
@@ -93,19 +93,56 @@ ${setupLines.join('\n')}
93
93
 
94
94
  ## House style
95
95
 
96
- - Never inline \`style={{}}\`use the helper props every component accepts (\`m*\`/\`p*\`
97
- spacing, \`textColor\`/\`bgColor\`, \`display="flex"\`, \`flexDirection\`, \`alignItems\`).
98
- Flex layouts have no \`gap\` helper — space children with margins (\`Grid\` and \`Columns\`
96
+ **Never inline \`style={{}}\`** — the components accept helper props that cover the common
97
+ cases. Before writing \`style\`, translate each declaration with this table:
98
+
99
+ | Inline style you're about to write | Helper props instead |
100
+ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
101
+ | \`marginTop: '1rem'\` (any margin/padding) | \`mt="4"\` — \`m\`/\`mt\`/\`mx\`/\`p\`/\`py\`/… scale: \`1\`=0.25rem, \`2\`=0.5rem, \`3\`=0.75rem, \`4\`=1rem, \`5\`=1.5rem, \`6\`=3rem (nearest step) |
102
+ | \`textAlign: 'center'\` | \`textAlign="centered"\` (also \`left\`, \`right\`, \`justified\`) |
103
+ | \`color: '#…'\` | \`textColor\` with the nearest Bulma color: \`primary\`, \`link\`, \`info\`, \`success\`, \`warning\`, \`danger\`, \`white\`, \`black\`, \`grey\` (+ \`grey-light\`, \`grey-dark\`, …) |
104
+ | \`backgroundColor: '#…'\` | \`bgColor\` (same palette) |
105
+ | \`fontSize: …\` | \`textSize="1"\`…\`"7"\` (\`1\` largest) — for headings use \`Title\`/\`SubTitle\` \`size\` |
106
+ | \`fontWeight: …\` | \`textWeight\`: \`light\`, \`normal\`, \`medium\`, \`semibold\`, \`bold\` |
107
+ | \`textTransform\`, italics | \`textTransform\`: \`uppercase\`, \`lowercase\`, \`capitalized\`, \`italic\` |
108
+ | \`display: 'flex'\` + flex properties | same-named props: \`display="flex"\`, \`flexDirection\`, \`justifyContent\`, \`alignItems\`, \`flexWrap\` |
109
+ | \`height: '100%'\` on a flex child | \`flexGrow="1"\` |
110
+ | \`display: 'none'\` | \`visibility="hidden"\`, or responsive \`display*\` props (\`displayMobile\`, \`displayTablet\`, …) |
111
+
112
+ - Spacing, typography, and flex helpers are on every component; \`textColor\`/\`bgColor\` are
113
+ on the content components (\`Box\`, \`Block\`, \`Title\`, \`Content\`, \`Hero\`, \`Card\`, …) — the
114
+ ones with a semantic \`color\` variant (\`Tag\`, \`Tabs\`, \`Panel\`) take \`color\` instead.
115
+ \`Notification\` is the mixed case: it takes \`textColor\`, but its background comes from
116
+ the semantic \`color\` prop, not \`bgColor\`.
117
+ - Flex layouts have no \`gap\` helper — space children with margins (\`Grid\` and \`Columns\`
99
118
  take a \`gap\` prop, so prefer that there).
119
+ - No helper matches (e.g. \`maxWidth\`, a one-off gradient)? Add a named class to
120
+ \`src/App.css\` and pass it via \`className\` — still never inline \`style\`.
121
+ - Don't hand-write Bulma utility classes either — bare text/markup has wrapper elements that
122
+ take the same helper props: \`Span\`, \`Paragraph\`, \`Strong\`, not \`<span className="has-text-…">\`.
123
+ The one exception: companion classes Bulma requires on \`<html>\`/\`<body>\` (e.g.
124
+ \`has-navbar-fixed-top\` with \`Navbar fixed="top"\`) are hand-added in \`index.html\` — no
125
+ component renders those elements.
126
+ - Compound sub-parts (\`Card.*\`, \`Modal.*\`, \`Tabs.*\`, \`Message.*\`) take \`className\` + HTML
127
+ attributes and their own few props — no Bulma helper props, no \`as\`/\`href\`: nest a
128
+ \`Link\`/\`Span\` inside instead. \`Tabs.Tab\` and \`Tabs.Content.Item\` each require \`index={i}\`,
129
+ and \`Tabs.Tab\` has built-in \`icon\`/\`disabled\` props — no nested \`Icon\` needed.
100
130
  - Compose existing components before writing custom CSS; theme via \`Theme\` and \`--bulma-*\`
101
131
  variables, never hardcoded colors.
102
132
  - \`Navbar.Burger\`/\`Navbar.Menu\` are controlled — wire \`active\` via state on both, and pair
103
133
  \`Navbar fixed="top"\` with the \`has-navbar-fixed-top\` class on \`<html>\` (never an inline
104
134
  padding offset).
135
+ - Reusable components you write get the library's spine so helper props work on them too:
136
+ extend \`BulmaClassesProps\`, run your props through \`useBulmaClasses\`, merge the
137
+ \`bulmaHelperClasses\` it returns into \`className\`, and spread **its** \`rest\` (not the raw
138
+ props) — the bestax-custom-component skill has the full template.
105
139
  - There is no test runner or Storybook in this app — don't assume one.
106
140
  - \`index.html\`'s \`<title>\` starts as the project name and \`README.md\` is stock template
107
141
  boilerplate — once this app has a real identity, set the title (and any meta tags) to match
108
142
  it and rewrite the README to describe *this* app, not the template.
143
+ - Before adding a dependency, match the package manager to the app's lockfile
144
+ (\`pnpm-lock.yaml\` → pnpm, \`package-lock.json\` → npm, \`yarn.lock\` → yarn) — a mismatched
145
+ install fails or forks the lockfile.
109
146
 
110
147
  ## AI skills
111
148
 
@@ -121,6 +158,7 @@ automatically when the task matches:
121
158
  - **bestax-migrate** — migrate code off react-bulma-components (v4): run the codemod, resolve its TODOs.
122
159
 
123
160
  Prefer the library's components and these skills over hand-written Bulma markup or custom CSS.
161
+ Read skill \`references/\` files with absolute paths — the shell's cwd is not stable between commands.
124
162
 
125
163
  \`.claude/launch.json\` declares this app's dev server for Claude Code's browser preview
126
164
  (\`npm run dev\` on port 5173, \`--strictPort\`) — start it from there rather than rediscovering
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bestax",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
4
4
  "description": "Create a new bestax-bulma project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -49,13 +49,23 @@ label — use that instead"_ or _"No `ProfileCard` exists; I'll build one compos
49
49
  ## Composition first
50
50
 
51
51
  Build from existing components before writing any CSS: `Box`, `Card`, `Title`, `SubTitle`,
52
- `Icon`, `Block`, `Content`, `Tag`, plus the Bulma helper props every component accepts (spacing,
53
- color, typography, flexbox). Most "custom components" are a composition function zero new
54
- styles. See `examples/stat-card.tsx` for a complete worked example.
52
+ `Icon`, `Block`, `Content`, `Tag`, plus the shared Bulma helper props (spacing, color,
53
+ typography, flexbox). Compound sub-parts are the exception`Card.Content`, `Modal.Card`,
54
+ `Tabs.Tab`, `Message.Body` take **no Bulma helper props**, just `className` + HTML attributes
55
+ plus their own few (`Tabs.Tab` requires `index={i}` and has built-in `disabled` and
56
+ `icon`/`iconLibrary`/`iconVariant`/`iconSize`/`iconFeatures` — don't nest an `<Icon>` there) —
57
+ so put helper props on the parent or on an element inside them, never invent them there. Most "custom components" are a composition function — zero new styles.
58
+ See `examples/stat-card.tsx` for a complete worked example.
55
59
 
56
60
  ## The component spine
57
61
 
58
- Same shape the library itself uses, with all imports from the package. File at
62
+ Same shape the library itself uses, with all imports from the package. Every reusable
63
+ component gets it — including pure compositions with zero CSS (a heading block, a labeled
64
+ wrapper): extend `BulmaClassesProps`, run your props through `useBulmaClasses`, merge its
65
+ `bulmaHelperClasses` into `className`, and spread the `rest` **it** returns — spreading the raw
66
+ props instead leaks helper props onto the DOM and emits none of their classes. The
67
+ `usePrefixedClassNames` root class is needed only when component-scoped CSS (or a variant
68
+ class) targets it — a zero-CSS composition may omit that call. File at
59
69
  `src/components/MyComponent.tsx`:
60
70
 
61
71
  ```tsx
@@ -101,8 +111,32 @@ free. `references/api.md` documents the helpers.
101
111
  ## Styling ladder — use the lowest rung that works
102
112
 
103
113
  **Rung 1 — helper props only (default).** House rules: never `style={{}}`. Layout with
104
- `Block`/`Box` and `display="flex"`, `flexDirection`, `alignItems`, `justifyContent`. There is
105
- **no `gap` helper** — space children with `m*`/`p*` margins instead.
114
+ `Block`/`Box` and `display="flex"`, `flexDirection`, `alignItems`, `justifyContent`. Flex
115
+ layouts have **no `gap` helper** — space children with `m*`/`p*` margins instead (`Grid` and
116
+ `Columns` take a `gap` prop). Before writing `style={{ … }}` anywhere, translate each
117
+ declaration:
118
+
119
+ | Inline style you're about to write | Helper props instead |
120
+ | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
121
+ | `marginTop: '1rem'` (any margin/padding) | `mt="4"` — `m`/`mt`/`mx`/`p`/`py`/… scale: `1`=0.25rem, `2`=0.5rem, `3`=0.75rem, `4`=1rem, `5`=1.5rem, `6`=3rem (nearest step) |
122
+ | `textAlign: 'center'` | `textAlign="centered"` (also `left`, `right`, `justified`) |
123
+ | `color: '#…'` | `textColor` with the nearest Bulma color: `primary`, `link`, `info`, `success`, `warning`, `danger`, `white`, `black`, `grey` (+ `grey-light`, `grey-dark`, …) |
124
+ | `backgroundColor: '#…'` | `bgColor` (same palette) |
125
+ | `fontSize: …` | `textSize="1"`…`"7"` (`1` largest) — for headings use `Title`/`SubTitle` `size` |
126
+ | `fontWeight: …` | `textWeight`: `light`, `normal`, `medium`, `semibold`, `bold` |
127
+ | `textTransform`, italics | `textTransform`: `uppercase`, `lowercase`, `capitalized`, `italic` |
128
+ | `display: 'flex'` + flex properties | same-named props: `display="flex"`, `flexDirection`, `justifyContent`, `alignItems`, `flexWrap` |
129
+ | `height: '100%'` on a flex child | `flexGrow="1"` |
130
+ | `display: 'none'` | `visibility="hidden"`, or responsive `display*` props (`displayMobile`, `displayTablet`, …) |
131
+
132
+ Spacing, typography, and flex helpers are on every component; `textColor`/`bgColor` are on
133
+ the content components you'll compose with (`Box`, `Block`, `Title`, `Content`, `Card`, …) —
134
+ the ones with a semantic `color` variant (`Tag`, `Tabs`, `Panel`) take `color` instead.
135
+ `Notification` is the mixed case: it takes `textColor`, but its background comes from the
136
+ semantic `color` prop, not `bgColor`.
137
+
138
+ A value with no helper equivalent (`maxWidth: 720`, a one-off gradient) moves you to rung 2 —
139
+ a named class in a stylesheet — never to inline `style`.
106
140
 
107
141
  **Rung 2 — a plain CSS file**, scoped under the component's class, consuming `--bulma-*`
108
142
  variables — never literal colors, so `Theme` and dark mode keep working:
@@ -139,7 +173,9 @@ bestax-bulma. Then the full `register-vars`/`getVar` pattern from
139
173
  Types don't see layout. Run `npm run dev`, render the component, and actually look at it:
140
174
  vertical centering of inline text (use `display="flex" alignItems="center"`, not line-height
141
175
  hacks), balanced padding, nothing clipping, every color/size variant, and **dark mode**
142
- legibility. Fix what you see, then re-check.
176
+ legibility. Fix what you see, then re-check. No browser available (headless)? Fall back to
177
+ `npm run build` plus a Node `renderToString` smoke render, grep the emitted HTML for the
178
+ expected classes, and flag the visual pass as not done.
143
179
 
144
180
  ## Tests and stories in an app
145
181
 
@@ -152,8 +188,9 @@ render, prop→class mapping, helper-prop passthrough (`m="3"` → `m-3`), and t
152
188
 
153
189
  - [ ] Inventory checked (catalog + bestax.io/docs/api) and the decision surfaced to the user.
154
190
  - [ ] All imports from `@allxsmith/bestax-bulma` (no deep/internal paths).
155
- - [ ] Composition first — existing components + helper props before any CSS.
156
- - [ ] No inline `style={{}}` anywhere.
191
+ - [ ] Composition first — existing components + helper props before any CSS; every reusable component gets the spine.
192
+ - [ ] No inline `style={{}}` anywhere — translate via the rung-1 mapping table; values with
193
+ no helper equivalent get a named class (rung 2).
157
194
  - [ ] Lowest sufficient ladder rung (helper props → scoped CSS vars → Sass).
158
195
  - [ ] All colors/radii derived from `--bulma-*` variables — no literals.
159
196
  - [ ] Renders correctly via `npm run dev`, including dark mode.
@@ -64,7 +64,11 @@ export function StatCard({
64
64
  size="large"
65
65
  textColor={color}
66
66
  mr="4"
67
- ariaLabel={`${label} icon`}
67
+ // Decorative: the label below already says it, so hide it from AT —
68
+ // Icon otherwise emits its default aria-label="icon". (To *label* an
69
+ // icon, use Icon's own camelCase `ariaLabel`; most components take
70
+ // the plain aria-label attribute.)
71
+ aria-hidden="true"
68
72
  />
69
73
  )}
70
74
  {/* No `gap` helper exists — space siblings with margin props (mr above). */}
@@ -30,7 +30,10 @@ that can also be used on their own:
30
30
  | Other | `useOtherClasses` | `float`, `overflow`, `radius`, `shadow`, `interaction`, `cursor`, `skeleton`, `clearfix`, `relative`, `fullHeight`, `responsive` |
31
31
 
32
32
  Because the component destructures these into `bulmaHelperClasses`, callers get the full Bulma
33
- helper surface for free on every component, and `rest` stays clean for DOM spreading.
33
+ helper surface for free on every component built this way, and `rest` stays clean for DOM
34
+ spreading. (Library compound sub-parts — `Card.Content`, `Modal.Card`, `Tabs.Tab`,
35
+ `Message.Body` — do **not** take helper props: just `className`, HTML attributes, and their own
36
+ few, e.g. `Tabs.Tab`'s required `index` and its built-in `icon`/`disabled` props.)
34
37
 
35
38
  ## `classNames(...)` and friends — `helpers/classNames.ts`
36
39
 
@@ -10,12 +10,30 @@ instead of hand-writing markup.
10
10
 
11
11
  - **Full props are not listed here** (that would be too large to keep in context).
12
12
  Follow a component's link for its complete prop table, or see the per-skill
13
- references. Every component also accepts the shared Bulma **helper props**
13
+ references. Value unions (`size`, `color`, variants) differ per component
14
+ never reuse one by analogy (`Tag size` is `normal|medium|large`; `Button`
15
+ adds `small`): the bestax-theming skill's
16
+ `references/themeable-components.md` lists them verbatim. The installed
17
+ types are at `node_modules/@allxsmith/bestax-bulma/dist/types/` (the
18
+ symlink resolves under pnpm's isolated linker — go straight there, no
19
+ `find` hunt), and when a `.d.ts` shows an opaque alias (`size?: TagSize`),
20
+ grep the alias name in that same file for the literals instead of guessing.
21
+ Top-level components accept the shared Bulma **helper props**
14
22
  (`m`/`p` spacing, `textColor`/`bgColor`, `textAlign`, `display`, flex, …) —
15
- documented once in `references/api.md`.
23
+ documented once in `references/api.md` — with rare exceptions (`Skeleton`).
16
24
  - **Compound components** expose sub-parts via dot access (e.g. `Card.Header`,
17
25
  `Navbar.Item`, `Tabs.Tab`, `Hero.Body`, `Columns.Column`, `Table.Tr`); see the
18
- component's linked page for the full set.
26
+ component's linked page for the full set. Sub-parts do **not** all take helper
27
+ props: the `Table.*`, `Menu.*`, and `Hero.*` families do (most `Navbar.*`
28
+ too), but `Card.*`, `Modal.*`, `Tabs.*`, and `Message.*` sub-parts take none —
29
+ just `className`, HTML attributes, and their own few (`Tabs.Tab` requires
30
+ `index` and has built-in `icon`/`disabled` props). Put helper props on the
31
+ parent or on an element inside (`Span`, `Paragraph`, …) instead.
32
+ - **Composing these into your own reusable component?** Use the spine in this
33
+ skill's `SKILL.md`: extend `BulmaClassesProps`, run your props through
34
+ `useBulmaClasses`, merge its `bulmaHelperClasses` into `className`, and spread
35
+ the `rest` it returns — so it takes the same helper props as the library
36
+ components.
19
37
  - Raw `*Base` form exports (`InputBase`, `SelectBase`, `TextAreaBase`, …) are
20
38
  escape-hatch variants of the convenience wrappers above them; see the Form docs.
21
39
 
@@ -11,8 +11,11 @@ This skill covers the form components in `@allxsmith/bestax-bulma` and how to co
11
11
  **Important:** bestax-bulma ships **no form/validation library** — there is no integration with
12
12
  formik, react-hook-form, yup, or zod, and no `useForm`-style hook. You own your form state with
13
13
  plain React (`useState` / `useReducer` or any library you choose) and feed validation results
14
- back into the components via the `color`, `message`, and `messageColor` props. See
15
- **Validation without a library** below.
14
+ back via each input's own `color`, `message`, and `messageColor` props on the convenience inputs
15
+ (`Input`, `Select`, `TextArea`, …). Always put validation state on the **input**: `Field` has no
16
+ `message`/`messageColor`, and although `FieldProps` types a `color`, `Field` discards it — it
17
+ renders no class, so setting it looks right and does nothing. (`FieldLabel`/`FieldBody` do honor
18
+ `color`, as the `has-text-*` helper.) See **Validation without a library** below.
16
19
 
17
20
  ## Use when
18
21
 
@@ -113,6 +116,13 @@ Across the convenience inputs (`Input`, `Select`, `TextArea`, and similar):
113
116
  Plus the full Bulma **helper props** (`m`, `p`, `textColor`, `display`, …) on every component
114
117
  via `useBulmaClasses`.
115
118
 
119
+ ⚠️ Full-width casing is inconsistent across the library: `Select`, `File`, and `Table` take
120
+ `isFullwidth` (lowercase w); `Button` alone takes `isFullWidth`; `Tabs` takes bare `fullwidth`.
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`).
125
+
116
126
  ## Convenience vs composed
117
127
 
118
128
  - **Convenience** (`<Input label message … />`) — for typical, single-control fields. Fewer
@@ -195,15 +205,19 @@ Before calling a form done, **render it and look at it**: run `pnpm storybook` (
195
205
  or the docs dev server, open the form, and check field alignment/spacing, the help-text/error
196
206
  states, and the validation flow (submit empty → fields turn `danger` with messages; fix → errors
197
207
  clear). If claude-in-chrome or Playwright is available, drive the browser and screenshot the
198
- valid and error states; otherwise eyeball it yourself.
208
+ valid and error states; otherwise eyeball it yourself. No browser at all (headless CI)? Fall
209
+ back to a production build plus a Node `renderToString` smoke render, grep the emitted HTML
210
+ for the expected classes/states, and say plainly that the visual pass is still owed.
199
211
 
200
212
  ## Checklist
201
213
 
202
214
  - [ ] Built from the shipped form components (no hand-rolled inputs / reinvented controls).
203
- - [ ] Every input has an associated label (`label` prop, or a `<label htmlFor>` when composing).
215
+ - [ ] Every label is programmatically associated: `label` prop + `id` on the input +
216
+ `labelProps={{ htmlFor }}`, or a `<label htmlFor>` when composing.
204
217
  - [ ] Controlled inputs have both `value` and `onChange` (or use `defaultValue` uncontrolled).
205
218
  - [ ] Error state shows via `color="danger"` + `message` + `messageColor="danger"`.
206
219
  - [ ] Grouped/addon layouts use explicit `Field` + `Control` composition.
207
220
  - [ ] No assumption of a built-in validation/form library — state is owned by the app.
208
221
  - [ ] **Rendered and visually inspected in a browser** — layout and the error/validation states
209
- look right, not just green tests.
222
+ look right, not just green tests. No browser available? The `renderToString` fallback above
223
+ counts only if you grepped the emitted classes/states **and** said the visual pass is owed.
@@ -16,7 +16,7 @@ Container and layout. Compound parts: `Field.Label`, `Field.Body`.
16
16
  | `narrow` | `boolean` | Constrain to content width (inside horizontal bodies). |
17
17
  | `label` | `ReactNode` | Convenience label. |
18
18
  | `labelSize` | `'small' \| 'normal' \| 'medium' \| 'large'` | Label size. |
19
- | `labelProps` | label attributes | Props for the `<label>`. |
19
+ | `labelProps` | label attributes | Props for the `<label>` — where `htmlFor` goes. |
20
20
  | `textColor` / `bgColor` | Bulma color | Helper colors for the field. |
21
21
 
22
22
  ## Control — `form/Control.tsx`
@@ -59,7 +59,8 @@ Wraps a single input; adds icons and loading.
59
59
  ## Select / SelectBase, TextArea / TextAreaBase
60
60
 
61
61
  Same convenience/raw split as Input. `Select` supports `isLoading` (on the control), `color`,
62
- `size`, `isRounded`, plus the Field/Control/message props. `TextArea` adds `rows` and
62
+ `size`, `isRounded`, `isFullwidth` (lowercase w Button's is `isFullWidth`), `multiple` +
63
+ `multipleSize`, plus the Field/Control/message props. `TextArea` adds `rows` and
63
64
  `hasFixedSize`.
64
65
 
65
66
  ## Checkbox / Checkboxes, Radio / Radios
@@ -61,7 +61,9 @@ don't rely on it.
61
61
 
62
62
  ## Accessibility
63
63
 
64
- Every `Icon` renders `aria-label` (default `"icon"`).
64
+ Every `Icon` renders `aria-label` (default `"icon"`), set via its camelCase `ariaLabel` prop.
65
+ Only a few components declare that prop (`Icon`, `Delete`, `Slider`, `Carousel`) — everything
66
+ else takes the standard `aria-label` attribute, e.g. `<Navbar.Burger aria-label="menu" />`.
65
67
 
66
68
  - **Meaningful icon** (stands alone, conveys information): pass a descriptive
67
69
  `ariaLabel="Delete item"`.
@@ -45,16 +45,57 @@ Centered; a collection of items → Card grid. For mixed requests, pick the domi
45
45
  `height: 100%` on the card doesn't help — the column's height is auto).
46
46
  - Rely on Bulma's responsive defaults: `Columns` sit side by side on tablet and up and stack on
47
47
  mobile. Add responsive `size*` props only to tune the breakpoints.
48
+ - Interactive extras don't share a state API — never transfer one by analogy:
49
+ `Collapse trigger={node} open/defaultOpen onOpen/onClose`, `Tabs value={i}/onChange`
50
+ (each `Tabs.Tab`/`Tabs.Content.Item` requires `index={i}`, and `Tabs.Content` must be a
51
+ **child of `<Tabs>`** — the active-tab context lives on it; a sibling panel never
52
+ switches), `Dropdown active/onActiveChange`,
53
+ `Steps value={i}/onStepClick items={[{label, icon?}]}` (child form is `Steps.Step`, not
54
+ `Steps.Item`). `Reveal cascade` staggers only its **direct children** — to stagger a grid,
55
+ put `<Reveal delay={i * 80}>` inside each `Cell`, not around the container.
56
+ - Link lists (footer nav, sidebars): a bare `UnorderedList` of `ListItem`s is already
57
+ marker-less and flush — Bulma's reset unstyles `ul` — so no prop or CSS is needed;
58
+ bullets appear only inside `Content`.
48
59
  - `Navbar.Burger`/`Navbar.Menu` are **controlled** — wire the same `active` state to both:
49
60
  `active` on `Navbar.Menu` shows/hides the mobile menu, while `active` + `onClick` on
50
61
  `Navbar.Burger` make the burger toggle it and animate. Left unwired, clicking the burger
51
62
  does nothing (no error, silent failure). For a `fixed="top"` `Navbar`, add the
52
63
  `has-navbar-fixed-top` class to `<html>` so content is not hidden behind it — the library
53
64
  does not do this automatically, and an inline padding offset is not a substitute.
54
- - **Style with helper props, not inline `style`.** Use `m`/`p` spacing (`mt="4"` = 1rem),
55
- `textAlign="centered"`, and `textColor`/`bgColor` instead of `style={{ marginTop, textAlign,
56
- color }}`. Set the app-wide icon library once with `<ConfigProvider iconLibrary="…">` at the root
57
- rather than `library` on every `<Icon>`.
65
+ - **Style with helper props no inline `style`, no raw Bulma `className`s.** Before writing
66
+ `style={{ }}` anywhere, translate each declaration with the mapping table below — the helper
67
+ props cover the common cases. Bare markup has wrapper elements that take all
68
+ helper props: `<Span textSize="7" textColor="grey">`, `Paragraph`, `Strong` never a raw
69
+ `<span className="is-size-7 has-text-grey">`. Table cells: `Th`/`Td` take `textAlign="right"`,
70
+ `textWeight`, `textSize` directly (their `color` prop colors the cell; for muted cell text
71
+ wrap content in `Span textColor="grey"`). Set the app-wide icon library once with
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:
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:
78
+
79
+ ```css
80
+ .hero-wash {
81
+ background-image: radial-gradient(
82
+ 60rem 30rem at 20% -10%,
83
+ hsl(var(--bulma-primary-h) var(--bulma-primary-s) 50% / 0.2),
84
+ transparent 60%
85
+ );
86
+ }
87
+ .section-alt {
88
+ background: var(--bulma-scheme-main-bis); /* next band: -ter */
89
+ }
90
+ ```
91
+
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.
98
+
58
99
  - **CTAs on a colored hero must stay legible in both schemes.** On a fixed-color surface
59
100
  (`Hero color="primary"`, a dark banner), use **filled** buttons — `color="light"` or
60
101
  `color="primary" isInverted` — never a thin `isOutlined` secondary: a light outline + light
@@ -63,6 +104,33 @@ color }}`. Set the app-wide icon library once with `<ConfigProvider iconLibrary=
63
104
  `<Theme isRoot colorMode="light">` — so a visitor's OS dark mode can't flip Bulma's text
64
105
  colors out from under the fixed palette (details: the `bestax-theming` skill's contrast rules).
65
106
 
107
+ ## Inline style → helper prop mapping
108
+
109
+ Look up the declaration you were about to inline. The spacing, typography, and flex helpers
110
+ below are on every component; `textColor`/`bgColor` are on the content components you'll
111
+ target (`Box`, `Block`, `Title`, `Content`, `Hero`, `Card`, …) — the ones with a semantic
112
+ `color` variant (`Tag`, `Tabs`, `Panel`) take `color` instead; wrap content in a `Block` if
113
+ you need a text color there. `Notification` is the mixed case: it takes `textColor`, but its
114
+ background comes from the semantic `color` prop, not `bgColor`.
115
+
116
+ | Inline style you're about to write | Helper props instead |
117
+ | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
118
+ | `marginTop: '1rem'` (any margin/padding) | `mt="4"` — `m`/`mt`/`mx`/`p`/`py`/… scale: `1`=0.25rem, `2`=0.5rem, `3`=0.75rem, `4`=1rem, `5`=1.5rem, `6`=3rem (nearest step) |
119
+ | `textAlign: 'center'` | `textAlign="centered"` (also `left`, `right`, `justified`) |
120
+ | `color: '#…'` | `textColor` with the nearest Bulma color: `primary`, `link`, `info`, `success`, `warning`, `danger`, `white`, `black`, `grey` (+ `grey-light`, `grey-dark`, …) |
121
+ | `backgroundColor: '#…'` | `bgColor` (same palette) |
122
+ | `fontSize: …` | `textSize="1"`…`"7"` (`1` largest) — for headings use `Title`/`SubTitle` `size` |
123
+ | `fontWeight: …` | `textWeight`: `light`, `normal`, `medium`, `semibold`, `bold` |
124
+ | `textTransform`, italics | `textTransform`: `uppercase`, `lowercase`, `capitalized`, `italic` |
125
+ | `display: 'flex'` + flex properties | same-named props: `display="flex"`, `flexDirection`, `justifyContent`, `alignItems`, `flexWrap` |
126
+ | `height: '100%'` on a flex child | `flexGrow="1"` |
127
+ | `display: 'none'` | `visibility="hidden"`, or responsive `display*` props (`displayMobile`, `displayTablet`, …) |
128
+ | `gap: …` in a flex layout | no `gap` helper exists — space children with `m*` margins; `Grid` and `Columns` take a `gap` prop, so prefer those there |
129
+
130
+ No helper matches (e.g. `maxWidth`, a one-off gradient)? Add a named class to the project
131
+ stylesheet (`src/App.css` in a scaffolded app) and pass it via `className` — still never
132
+ inline `style`.
133
+
66
134
  ## References
67
135
 
68
136
  - `references/layout-components.md` — the layout component inventory: real prop names, types, and
@@ -89,5 +157,11 @@ color }}`. Set the app-wide icon library once with `<ConfigProvider iconLibrary=
89
157
  - [ ] Wire `active` state to **both** `Navbar.Burger` and `Navbar.Menu` (they are controlled).
90
158
  - [ ] For a fixed navbar, add `has-navbar-fixed-top` to `<html>`.
91
159
  - [ ] Do not use `Tile` — it is not shipped.
92
- - [ ] Style with helper props (`mt`/`p`, `textAlign`, `textColor`), not inline `style`.
160
+ - [ ] Style with helper props, not inline `style` translate via the mapping table; values
161
+ with no helper get a named class in the stylesheet, never `style={{}}`. No raw Bulma
162
+ `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.
93
165
  - [ ] Set the icon library once via `<ConfigProvider iconLibrary="…">` at the root.
166
+ - [ ] Site built? ~800 KB raw / ~82 KB gzip CSS is the expected default-flavor size — to shrink
167
+ it, run the `bestax-optimize` skill (measure first).
@@ -3,7 +3,9 @@
3
3
  //
4
4
  // A fixed-top navbar needs the `has-navbar-fixed-top` class on <html> so the page
5
5
  // is padded below it — Bulma requires this and the library does NOT add it for
6
- // you. The columns sit side by side on tablet and up, and stack (menu above
6
+ // you. In a real app set it statically in index.html; the useEffect below is the
7
+ // fallback for a conditionally-mounted navbar (and keeps this file self-contained).
8
+ // The columns sit side by side on tablet and up, and stack (menu above
7
9
  // content) on mobile.
8
10
  //
9
11
  // `ConfigProvider` wraps the shell once at the root to set the app-wide icon
@@ -90,9 +90,10 @@ export default function CatalogPage() {
90
90
  image={product.image}
91
91
  imageAlt={product.name}
92
92
  header={product.name}
93
- footer={
94
- <span className="card-footer-item">{product.price}</span>
95
- }
93
+ // Card wraps each footer item in .card-footer-item itself —
94
+ // no raw span/className needed (and a literal class would
95
+ // break under ConfigProvider classPrefix).
96
+ footer={product.price}
96
97
  >
97
98
  <p>{product.blurb}</p>
98
99
  </Card>
@@ -22,6 +22,7 @@ app".
22
22
  const [open, setOpen] = useState(false); // controls the burger + mobile menu
23
23
 
24
24
  <>
25
+ {/* fixed="top" requires <html class="has-navbar-fixed-top"> — set it in index.html */}
25
26
  <Navbar fixed="top" color="dark">
26
27
  <Navbar.Brand>
27
28
  <Navbar.Item href="#">Brand</Navbar.Item>
@@ -59,8 +60,9 @@ const [open, setOpen] = useState(false); // controls the burger + mobile menu
59
60
  </>;
60
61
  ```
61
62
 
62
- **Required:** add `has-navbar-fixed-top` to `<html>` (see `examples/app-shell.tsx`) so content is
63
- not hidden behind the fixed navbar.
63
+ **Required:** add `has-navbar-fixed-top` to `<html>` statically in `index.html`; an effect
64
+ (`examples/app-shell.tsx`) only for a conditionally-mounted navbar — so content is not hidden
65
+ behind the fixed navbar.
64
66
 
65
67
  **Responsive:** the navbar collapses to a burger on mobile (`Navbar.Burger` + `Navbar.Menu active`).
66
68
  The sidebar and content columns sit side by side on tablet and up, and stack (menu above content)
@@ -148,6 +150,14 @@ the fixed navbar — never an inline padding offset.
148
150
  `Section`s already stack vertically. The feature `Columns` collapse to one feature
149
151
  per row on mobile. Use `Hero size="large"` / `"fullheight"` for a taller hero.
150
152
 
153
+ **Site chrome:** a full site adds the App-shell `Navbar` (archetype 1, minus the sidebar) above
154
+ the `Hero`; if it's `fixed="top"`, the same `<html class="has-navbar-fixed-top">` requirement
155
+ applies here too.
156
+
157
+ **Alternating section bands:** tint every other `Section` with a scheme step —
158
+ `.section-alt { background: var(--bulma-scheme-main-bis); }` (next step `-ter`) — not
159
+ `bgColor="light"`/`"white"`: those are fixed colors that stay light when dark mode flips the text.
160
+
151
161
  **Hero CTAs:** on a colored hero use **filled** buttons only — `color="light"` for the primary
152
162
  CTA and `color="primary" isInverted` (solid white, primary text) for a secondary. A thin
153
163
  `isOutlined` button on a fixed-color surface is low-contrast and degrades further under OS dark
@@ -204,7 +214,7 @@ search results, "a grid of cards".
204
214
  flexGrow="1"
205
215
  image={item.image}
206
216
  header={item.name}
207
- footer={<span className="card-footer-item">{item.price}</span>}
217
+ footer={item.price} // Card wraps footer items in .card-footer-item itself
208
218
  >
209
219
  {item.blurb}
210
220
  </Card>
@@ -21,7 +21,8 @@ import {
21
21
  ```
22
22
 
23
23
  Every component also accepts the shared Bulma helper props (`m`/`p` spacing, `textAlign`,
24
- `textColor`, `bgColor`, etc.).
24
+ `textColor`, `bgColor`, etc.). Flex helpers take the standard CSS values spelled in full:
25
+ `justifyContent="space-between"`, `alignItems="center"`.
25
26
 
26
27
  > **Use helper props, never inline `style`, for spacing / alignment / color.** `mt="4"` (= 1rem)
27
28
  > not `style={{ marginTop: '1rem' }}`; `textAlign="centered"` not `style={{ textAlign: 'center' }}`;
@@ -129,6 +130,9 @@ type BulmaColumnSize =
129
130
  | 'four-fifths';
130
131
  ```
131
132
 
133
+ Numeric sizes are **numbers** — `sizeDesktop={7}`, never `"7"`; only the fraction names are
134
+ strings. (`gap` is the exception that accepts number **or** string.)
135
+
132
136
  > Columns **stack on mobile** by default and go side-by-side at the tablet breakpoint and up.
133
137
  > Use the per-breakpoint `size*` props to control how many cells share a row at each width.
134
138
 
@@ -219,11 +223,9 @@ flexDirection="column"` + `flexGrow="1"` pattern applies to `Cell` + `Card`.
219
223
 
220
224
  The burger/menu is controlled state — toggle `Navbar.Burger active`/`onClick` and pass the same
221
225
  flag to `Navbar.Menu active`. **A `fixed="top"` navbar requires `has-navbar-fixed-top` on `<html>`**
222
- (Bulma offsets the page from it); the library adds no helper, so set it yourself:
223
-
224
- ```ts
225
- document.documentElement.classList.add('has-navbar-fixed-top');
226
- ```
226
+ (Bulma offsets the page from it); the library adds no helper. Set it statically —
227
+ `<html class="has-navbar-fixed-top">` in `index.html` — reserving a `classList.add` effect for
228
+ a navbar that mounts conditionally.
227
229
 
228
230
  **Routing:** in a routed app, don't use `href="#"` — render items as the router's link
229
231
  component. `Menu.Item as={Link} to="/x"` and `Navbar.Item as={Link} to="/x"` both compile
@@ -9,12 +9,6 @@ license: MIT
9
9
  `@allxsmith/bestax-bulma` wraps Bulma 1.x, which is themed through `--bulma-*` CSS custom
10
10
  properties. Theme an app by overriding the right variables — no component re-styling required.
11
11
 
12
- ## Use when
13
-
14
- - Setting a brand/primary color or recoloring `link`/`info`/`success`/`warning`/`danger`.
15
- - Adjusting global tokens — radius, fonts, sizes, weights.
16
- - Adding light/dark mode.
17
-
18
12
  ## Approach
19
13
 
20
14
  Recolor a brand color by overriding its **hue/saturation/lightness trio** — Bulma derives every
@@ -50,13 +44,21 @@ color tokens or fixed-color surfaces exist:
50
44
  (`--my-canvas: var(--bulma-scheme-main)`) — or flip them yourself under **both** dark-mode
51
45
  paths: `[data-theme='dark']` **and** `@media (prefers-color-scheme: dark)` scoped to
52
46
  `:root:not([data-theme])`, since `colorMode="system"` removes the attribute (snippets in
53
- `references/css-variables.md`).
47
+ `references/css-variables.md`). Alternating/tinted section bands are this case:
48
+ `background: var(--bulma-scheme-main-bis)` (then `-ter`), never `bgColor="light"` —
49
+ `light`/`white`/grey helper backgrounds are fixed colors that fight dark mode.
54
50
  - **Fixed-color surface → fixed-color content.** On a surface that never changes (a dark hero,
55
51
  a brand banner), pin the content's colors too: solid/filled buttons and explicit text colors,
56
52
  never scheme-derived defaults or thin outlines that depend on the flipping scheme.
57
53
 
58
54
  Reach for the helper props (`color` / `textColor` / `bgColor` / `colorShade`, `textSize`,
59
55
  `textWeight`, `fontFamily`) to apply themed colors and type to individual components.
56
+ Variant flags and value unions are component-specific — never carry one over by analogy:
57
+ `isLight` exists on `Button` and `Notification` **only** (`Tag` has none, and `LinkButtonProps`
58
+ omits it);
59
+ `Tag size` is `normal | medium | large` (no `small`, unlike `Button`); `Buttons` has
60
+ `isCentered`, `Tags` does not (center tags with `justifyContent="center"`); the verbatim
61
+ truth table is `references/themeable-components.md`.
60
62
 
61
63
  ## Quick start
62
64
 
@@ -109,7 +111,7 @@ Nest `Theme` and `ConfigProvider` together at the root (order doesn't matter).
109
111
 
110
112
  - [ ] Recolor brand colors via the HSL trio (`*-h` / `*-s` / `*-l`), not by hard-coding hex on components.
111
113
  - [ ] Apply a global theme once with `<Theme isRoot>` (or `:root`); use scoped `<Theme>` for one-off sections.
112
- - [ ] Set non-color tokens (radius, fonts, sizes) through `bulmaVars` or `:root`.
114
+ - [ ] Set non-color tokens (radius, fonts, sizes) through `bulmaVars` or `:root`; a custom `--bulma-family-*` needs its font actually loaded (`index.html` `<link>` or an `@fontsource` import).
113
115
  - [ ] Implement dark mode with `data-theme` on `<html>`; do not expect a shipped dark-mode component.
114
116
  - [ ] Pass `color`/`textColor`/`bgColor` (not custom CSS) to color individual components.
115
117
  - [ ] Set the icon library once with `<ConfigProvider iconLibrary="…">` at the root, not `library` on every `<Icon>`.
@@ -34,6 +34,8 @@ export function ThemedApp({ children }: { children: React.ReactNode }) {
34
34
  bulmaVars={{
35
35
  '--bulma-radius': '0.75rem',
36
36
  '--bulma-radius-large': '1.25rem',
37
+ // 'Inter' must actually be loaded (index.html <link> or an @fontsource
38
+ // import) — declaring the family var alone falls back to system-ui.
37
39
  '--bulma-family-primary': "'Inter', system-ui, sans-serif",
38
40
  }}
39
41
  >
@@ -33,6 +33,10 @@ theming means overriding the right `--bulma-*` values.
33
33
  > Note: the type of the `bulmaVars` keys is not exported — pass it as an object literal (TypeScript
34
34
  > still checks the keys against the allowed `--bulma-*` names).
35
35
 
36
+ > `--bulma-family-*` only selects the family — also load the font itself (a `<link>` in
37
+ > `index.html` or an `@fontsource/*` package import), or the browser silently falls back to
38
+ > the system font.
39
+
36
40
  ### 2. Plain CSS
37
41
 
38
42
  Set the variables yourself on any selector. `:root` themes the whole document; a class scopes it.
@@ -8,13 +8,25 @@ This is the self-contained inventory of the color/size/variant props that matter
8
8
 
9
9
  1. **Component `color` modifier** → emits `is-<color>` (the filled Bulma variant). The accepted
10
10
  values are component-specific (see the table). Example: `<Button color="primary">` → `is-primary`.
11
- 2. **Helper color props** (available on virtually every component, applied as utility classes):
11
+ ⚠️ Some unions are **typed wider than the CSS Bulma ships** the class is emitted but no rule
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`.
16
+ 2. **Helper color props** (on most components, applied as utility classes):
12
17
  - `color` / `textColor` → `has-text-<color>` (text color)
13
18
  - `backgroundColor` / `bgColor` → `has-background-<color>` (background)
14
19
  - `colorShade` / `backgroundColorShade` → adds a shade suffix, e.g. `has-text-primary-30`
15
20
 
16
- When a component has its own `color` modifier (Button, Tag, Input, …), use **`textColor`** /
17
- **`bgColor`** for utility coloring so the two don't collide.
21
+ Components with a real `is-<color>` modifier (`Button`, `Hero`) drop the `color` helper and
22
+ re-expose it as **`textColor`** / **`bgColor`**. `Box`/`Card`/`Section` ship no `is-<color>`
23
+ 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
25
+ **no text-color prop** — wrap content in `<Span textColor="…">`. `Input` has none either and
26
+ the wrapper trick can't work (it renders a native `<input>`; a child can't color its value):
27
+ recolor via the upstream `--bulma-text-strong-l`, since Bulma re-declares `--bulma-input-*` on
28
+ `.input` itself and an ancestor `<Theme>` can't reach those. Raw `backgroundColor` still works
29
+ on `Tag` and `Input`.
18
30
 
19
31
  `<color>` for the helper props is one of **`validColors`**:
20
32
 
@@ -28,43 +40,43 @@ Shades (`colorShade` / `backgroundColorShade`): `00, 05, 10, … 95, invert, lig
28
40
 
29
41
  ## Component `color` / `size` props (verbatim unions)
30
42
 
31
- | Component | `color` accepts | `size` accepts | Notes |
32
- | ------------------ | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
33
- | `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` |
34
- | `Notification` | the 17 `validColors` | — | also `isLight` |
35
- | `Tag` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `normal \| medium \| large` | also `isRounded`, `isDelete`, `isHoverable` |
36
- | `Box` | `primary \| link \| info \| success \| warning \| danger` | — | the 6 only; also `hasShadow` |
37
- | `Message` | `primary \| link \| info \| success \| warning \| danger` | — | the 6 only |
38
- | `Input` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isRounded`, `isStatic` |
39
- | `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` |
40
- | `Badge` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | pill background; default `danger` |
41
- | `Title` | — (no `color`; use `textColor`) | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | also `isSpaced` |
42
- | `SubTitle` | — (no `color`; use `textColor`) | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | — |
43
- | `Autocomplete` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | the 6 only |
44
- | `Checkbox` | `primary \| link \| info \| success \| warning \| danger` | `small \| normal \| medium \| large` | the 6 only |
45
- | `DateInput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `isRounded` |
46
- | `DateTimeInput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `isRounded` |
47
- | `File` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isBoxed`, `isFullwidth` |
48
- | `Hero` | the 17 `validColors` | `small \| medium \| large \| fullheight \| fullheight-with-navbar` | section background |
49
- | `LinkButton` | `primary \| link \| info \| success \| warning \| danger \| white \| light \| dark \| black` | — | button-styled link; emits `link-button-<color>` |
50
- | `Loading` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | spinner color; default light grey |
51
- | `Navbar` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | — |
52
- | `Numberinput` | `primary \| link \| info \| success \| warning \| danger \| light \| dark` | `small \| medium \| large` | also `inputColor` (the 6) for the inner input |
53
- | `Pagination` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | — |
54
- | `Panel` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | — |
55
- | `Progress` | the 17 `validColors` | `small \| medium \| large` | — |
56
- | `Radio` | `primary \| link \| info \| success \| warning \| danger` | `small \| normal \| medium \| large` | the 6 only |
57
- | `Rate` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | the 6 only |
58
- | `Select` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isRounded` |
59
- | `Slider` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `isRounded`, `isCircle` |
60
- | `Steps` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | the 6 only |
61
- | `Switch` | `primary \| link \| info \| success \| warning \| danger` | `small \| normal \| medium \| large` | also `isRounded`, `isThin`, `isOutlined` |
62
- | `Tabs` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | — |
63
- | `Taginput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `tagColor` (the 6 + `dark \| light`) for the tags |
64
- | `TextArea` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isRounded`, `isStatic` |
65
- | `TimeInput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | — |
66
- | `Tooltip` | `primary \| link \| info \| success \| warning \| danger \| dark \| light` | `small \| medium \| large` | — |
67
- | `Tr` / `Td` / `Th` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | table row/cell background |
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 |
68
80
 
69
81
  The 6 brand colors (`primary, link, info, success, warning, danger`) are the ones a custom theme
70
82
  recolors via the HSL trios (see `css-variables.md`). The greyscale and `white`/`light`/`dark`