Skip to content
Merged
18 changes: 16 additions & 2 deletions create-bestax/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,29 @@ ${setupLines.join('\n')}

## House style

- Never inline \`style={{}}\` — use the helper props every component accepts (\`m*\`/\`p*\`
spacing, \`textColor\`/\`bgColor\`, \`display="flex"\`, \`flexDirection\`, \`alignItems\`).
- Never inline \`style={{}}\` and never hand-write Bulma utility classes — use the shared
helper props (\`m*\`/\`p*\` spacing, \`textColor\`/\`bgColor\`, \`display="flex"\`, \`flexDirection\`,
\`alignItems\`). Bare text/markup has wrapper elements that take the same helper props:
\`Span\`, \`Paragraph\`, \`Strong\` — not \`<span className="has-text-…">\`. The one exception:
companion classes Bulma requires on \`<html>\`/\`<body>\` (e.g. \`has-navbar-fixed-top\` with
\`Navbar fixed="top"\`) are hand-added in \`index.html\` — no component renders those elements.
Flex layouts have no \`gap\` helper — space children with margins (\`Grid\` and \`Columns\`
take a \`gap\` prop, so prefer that there).
- Compound sub-parts (\`Card.*\`, \`Modal.*\`, \`Tabs.*\`, \`Message.*\`) take only \`className\` +
HTML attributes — no helper props, no \`as\`/\`href\`: nest a \`Link\`/\`Span\` inside instead.
One required addition: \`Tabs.Tab\` and \`Tabs.Content.Item\` each need \`index={i}\`.
- Compose existing components before writing custom CSS; theme via \`Theme\` and \`--bulma-*\`
variables, never hardcoded colors.
- Reusable components you write get the library's spine so helper props work on them too:
extend \`BulmaClassesProps\`, merge \`className\`, spread \`...rest\` — the
bestax-custom-component skill has the full template.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
- There is no test runner or Storybook in this app — don't assume one.
- \`index.html\`'s \`<title>\` starts as the project name and \`README.md\` is stock template
boilerplate — once this app has a real identity, set the title (and any meta tags) to match
it and rewrite the README to describe *this* app, not the template.
- Before adding a dependency, match the package manager to the app's lockfile
(\`pnpm-lock.yaml\` → pnpm, \`package-lock.json\` → npm, \`yarn.lock\` → yarn) — a mismatched
install fails or forks the lockfile.

## AI skills

Expand All @@ -158,6 +171,7 @@ automatically when the task matches:
- **bestax-migrate** — migrate code off react-bulma-components (v4): run the codemod, resolve its TODOs.

Prefer the library's components and these skills over hand-written Bulma markup or custom CSS.
Read skill \`references/\` files with absolute paths — the shell's cwd is not stable between commands.

\`.claude/launch.json\` declares this app's dev server for Claude Code's browser preview
(\`npm run dev\` on port 5173, \`--strictPort\`) — start it from there rather than rediscovering
Expand Down
21 changes: 18 additions & 3 deletions scripts/gen-component-catalog.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,27 @@ instead of hand-writing markup.

- **Full props are not listed here** (that would be too large to keep in context).
Follow a component's link for its complete prop table, or see the per-skill
references. Every component also accepts the shared Bulma **helper props**
references. Value unions (\`size\`, \`color\`, variants) differ per component —
never reuse one by analogy (\`Tag size\` is \`normal|medium|large\`; \`Button\`
adds \`small\`): the bestax-theming skill's
\`references/themeable-components.md\` lists them verbatim. The installed
types are at \`node_modules/@allxsmith/bestax-bulma/dist/types/\` (the
symlink resolves under pnpm's isolated linker — go straight there, no
\`find\` hunt), and when a \`.d.ts\` shows an opaque alias (\`size?: TagSize\`),
grep the alias name in that same file for the literals instead of guessing.
Top-level components accept the shared Bulma **helper props**
(\`m\`/\`p\` spacing, \`textColor\`/\`bgColor\`, \`textAlign\`, \`display\`, flex, …) —
documented once in \`references/api.md\`.
documented once in \`references/api.md\` — with rare exceptions (\`Skeleton\`).
- **Compound components** expose sub-parts via dot access (e.g. \`Card.Header\`,
\`Navbar.Item\`, \`Tabs.Tab\`, \`Hero.Body\`, \`Columns.Column\`, \`Table.Tr\`); see the
component's linked page for the full set.
component's linked page for the full set. Sub-parts do **not** all take helper
props: the \`Table.*\`, \`Menu.*\`, and \`Hero.*\` families do (most \`Navbar.*\`
too), but \`Card.*\`, \`Modal.*\`, \`Tabs.*\`, and \`Message.*\` sub-parts take only
\`className\` + HTML attributes — put helper props on the parent or on an
element inside (\`Span\`, \`Paragraph\`, …) instead.
- **Composing these into your own reusable component?** Use the spine in this
skill's \`SKILL.md\`: extend \`BulmaClassesProps\`, merge \`className\`, spread
\`...rest\` — so it takes the same helper props as the library components.
- Raw \`*Base\` form exports (\`InputBase\`, \`SelectBase\`, \`TextAreaBase\`, …) are
escape-hatch variants of the convenience wrappers above them; see the Form docs.

Expand Down
21 changes: 15 additions & 6 deletions skills/bestax-custom-component/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,20 @@ label — use that instead"_ or _"No `ProfileCard` exists; I'll build one compos
## Composition first

Build from existing components before writing any CSS: `Box`, `Card`, `Title`, `SubTitle`,
`Icon`, `Block`, `Content`, `Tag`, plus the Bulma helper props every component accepts (spacing,
color, typography, flexbox). Most "custom components" are a composition function — zero new
styles. See `examples/stat-card.tsx` for a complete worked example.
`Icon`, `Block`, `Content`, `Tag`, plus the shared Bulma helper props (spacing, color,
typography, flexbox). Compound sub-parts are the exception — `Card.Content`, `Modal.Card`,
`Tabs.Tab`, `Message.Body` take only `className` + HTML attributes — `Tabs.Tab` additionally
requires `index={i}` — 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.
See `examples/stat-card.tsx` for a complete worked example.

## The component spine

Same shape the library itself uses, with all imports from the package. File at
Same shape the library itself uses, with all imports from the package. Every reusable
component gets it — including pure compositions with zero CSS (a heading block, a labeled
wrapper): extend `BulmaClassesProps`, merge `className`, spread `...rest`. The
`usePrefixedClassNames` root class is needed only when component-scoped CSS (or a variant
class) targets it — a zero-CSS composition may omit that call. File at
`src/components/MyComponent.tsx`:

```tsx
Expand Down Expand Up @@ -139,7 +146,9 @@ bestax-bulma. Then the full `register-vars`/`getVar` pattern from
Types don't see layout. Run `npm run dev`, render the component, and actually look at it:
vertical centering of inline text (use `display="flex" alignItems="center"`, not line-height
hacks), balanced padding, nothing clipping, every color/size variant, and **dark mode**
legibility. Fix what you see, then re-check.
legibility. Fix what you see, then re-check. No browser available (headless)? Fall back to
`npm run build` plus a Node `renderToString` smoke render, grep the emitted HTML for the
expected classes, and flag the visual pass as not done.

## Tests and stories in an app

Expand All @@ -152,7 +161,7 @@ render, prop→class mapping, helper-prop passthrough (`m="3"` → `m-3`), and t

- [ ] Inventory checked (catalog + bestax.io/docs/api) and the decision surfaced to the user.
- [ ] All imports from `@allxsmith/bestax-bulma` (no deep/internal paths).
- [ ] Composition first — existing components + helper props before any CSS.
- [ ] Composition first — existing components + helper props before any CSS; every reusable component gets the spine.
- [ ] No inline `style={{}}` anywhere.
- [ ] Lowest sufficient ladder rung (helper props → scoped CSS vars → Sass).
- [ ] All colors/radii derived from `--bulma-*` variables — no literals.
Expand Down
1 change: 1 addition & 0 deletions skills/bestax-custom-component/examples/stat-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export function StatCard({
size="large"
textColor={color}
mr="4"
// ariaLabel is Icon's own prop; most components take plain aria-label.
ariaLabel={`${label} icon`}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
/>
)}
Expand Down
4 changes: 3 additions & 1 deletion skills/bestax-custom-component/references/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ that can also be used on their own:
| Other | `useOtherClasses` | `float`, `overflow`, `radius`, `shadow`, `interaction`, `cursor`, `skeleton`, `clearfix`, `relative`, `fullHeight`, `responsive` |

Because the component destructures these into `bulmaHelperClasses`, callers get the full Bulma
helper surface for free on every component, and `rest` stays clean for DOM spreading.
helper surface for free on every component built this way, and `rest` stays clean for DOM
spreading. (Library compound sub-parts — `Card.Content`, `Modal.Card`, `Tabs.Tab`,
`Message.Body` — do **not** take helper props: only `className` + HTML attributes.)

## `classNames(...)` and friends — `helpers/classNames.ts`

Expand Down
21 changes: 18 additions & 3 deletions skills/bestax-custom-component/references/component-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,27 @@ instead of hand-writing markup.

- **Full props are not listed here** (that would be too large to keep in context).
Follow a component's link for its complete prop table, or see the per-skill
references. Every component also accepts the shared Bulma **helper props**
references. Value unions (`size`, `color`, variants) differ per component —
never reuse one by analogy (`Tag size` is `normal|medium|large`; `Button`
adds `small`): the bestax-theming skill's
`references/themeable-components.md` lists them verbatim. The installed
types are at `node_modules/@allxsmith/bestax-bulma/dist/types/` (the
symlink resolves under pnpm's isolated linker — go straight there, no
`find` hunt), and when a `.d.ts` shows an opaque alias (`size?: TagSize`),
grep the alias name in that same file for the literals instead of guessing.
Top-level components accept the shared Bulma **helper props**
(`m`/`p` spacing, `textColor`/`bgColor`, `textAlign`, `display`, flex, …) —
documented once in `references/api.md`.
documented once in `references/api.md` — with rare exceptions (`Skeleton`).
- **Compound components** expose sub-parts via dot access (e.g. `Card.Header`,
`Navbar.Item`, `Tabs.Tab`, `Hero.Body`, `Columns.Column`, `Table.Tr`); see the
component's linked page for the full set.
component's linked page for the full set. Sub-parts do **not** all take helper
props: the `Table.*`, `Menu.*`, and `Hero.*` families do (most `Navbar.*`
too), but `Card.*`, `Modal.*`, `Tabs.*`, and `Message.*` sub-parts take only
`className` + HTML attributes — put helper props on the parent or on an
element inside (`Span`, `Paragraph`, …) instead.
- **Composing these into your own reusable component?** Use the spine in this
skill's `SKILL.md`: extend `BulmaClassesProps`, merge `className`, spread
`...rest` — so it takes the same helper props as the library components.
- Raw `*Base` form exports (`InputBase`, `SelectBase`, `TextAreaBase`, …) are
escape-hatch variants of the convenience wrappers above them; see the Form docs.

Expand Down
19 changes: 15 additions & 4 deletions skills/bestax-form/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ This skill covers the form components in `@allxsmith/bestax-bulma` and how to co
**Important:** bestax-bulma ships **no form/validation library** — there is no integration with
formik, react-hook-form, yup, or zod, and no `useForm`-style hook. You own your form state with
plain React (`useState` / `useReducer` or any library you choose) and feed validation results
back into the components via the `color`, `message`, and `messageColor` props. See
**Validation without a library** below.
back via each input's own `color`, `message`, and `messageColor` props — they live on the
convenience inputs (`Input`, `Select`, `TextArea`, …), **not** on `Field` (which takes `label`
but has no `message`). See **Validation without a library** below.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

## Use when

Expand Down Expand Up @@ -113,6 +114,13 @@ Across the convenience inputs (`Input`, `Select`, `TextArea`, and similar):
Plus the full Bulma **helper props** (`m`, `p`, `textColor`, `display`, …) on every component
via `useBulmaClasses`.

⚠️ Full-width casing is inconsistent across the library: `Select`, `File`, and `Table` take
`isFullwidth` (lowercase w); `Button` alone takes `isFullWidth`; `Tabs` takes bare `fullwidth`.

⚠️ The `label` prop renders the `<label>` but does **not** wire `htmlFor`/`id` — assistive tech
gets no association. Pass `id` on the input plus `labelProps={{ htmlFor: sameId }}` (every
convenience input and `Field` accept `labelProps`).

## Convenience vs composed

- **Convenience** (`<Input label message … />`) — for typical, single-control fields. Fewer
Expand Down Expand Up @@ -195,12 +203,15 @@ Before calling a form done, **render it and look at it**: run `pnpm storybook` (
or the docs dev server, open the form, and check field alignment/spacing, the help-text/error
states, and the validation flow (submit empty → fields turn `danger` with messages; fix → errors
clear). If claude-in-chrome or Playwright is available, drive the browser and screenshot the
valid and error states; otherwise eyeball it yourself.
valid and error states; otherwise eyeball it yourself. No browser at all (headless CI)? Fall
back to a production build plus a Node `renderToString` smoke render, grep the emitted HTML
for the expected classes/states, and say plainly that the visual pass is still owed.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

## Checklist

- [ ] Built from the shipped form components (no hand-rolled inputs / reinvented controls).
- [ ] Every input has an associated label (`label` prop, or a `<label htmlFor>` when composing).
- [ ] Every label is programmatically associated: `label` prop + `id` on the input +
`labelProps={{ htmlFor }}`, or a `<label htmlFor>` when composing.
- [ ] Controlled inputs have both `value` and `onChange` (or use `defaultValue` uncontrolled).
- [ ] Error state shows via `color="danger"` + `message` + `messageColor="danger"`.
- [ ] Grouped/addon layouts use explicit `Field` + `Control` composition.
Expand Down
5 changes: 3 additions & 2 deletions skills/bestax-form/references/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Container and layout. Compound parts: `Field.Label`, `Field.Body`.
| `narrow` | `boolean` | Constrain to content width (inside horizontal bodies). |
| `label` | `ReactNode` | Convenience label. |
| `labelSize` | `'small' \| 'normal' \| 'medium' \| 'large'` | Label size. |
| `labelProps` | label attributes | Props for the `<label>`. |
| `labelProps` | label attributes | Props for the `<label>` — where `htmlFor` goes. |
| `textColor` / `bgColor` | Bulma color | Helper colors for the field. |

## Control — `form/Control.tsx`
Expand Down Expand Up @@ -59,7 +59,8 @@ Wraps a single input; adds icons and loading.
## Select / SelectBase, TextArea / TextAreaBase

Same convenience/raw split as Input. `Select` supports `isLoading` (on the control), `color`,
`size`, `isRounded`, plus the Field/Control/message props. `TextArea` adds `rows` and
`size`, `isRounded`, `isFullwidth` (lowercase w — Button's is `isFullWidth`), `multiple` +
`multipleSize`, plus the Field/Control/message props. `TextArea` adds `rows` and
`hasFixedSize`.

## Checkbox / Checkboxes, Radio / Radios
Expand Down
4 changes: 3 additions & 1 deletion skills/bestax-icons/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ don't rely on it.

## Accessibility

Every `Icon` renders `aria-label` (default `"icon"`).
Every `Icon` renders `aria-label` (default `"icon"`), set via its camelCase `ariaLabel` prop.
Only a few components declare that prop (`Icon`, `Delete`, `Slider`, `Carousel`) — everything
else takes the standard `aria-label` attribute, e.g. `<Navbar.Burger aria-label="menu" />`.

- **Meaningful icon** (stands alone, conveys information): pass a descriptive
`ariaLabel="Delete item"`.
Expand Down
Loading
Loading