create-bestax 4.2.17 → 4.2.19

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.
Files changed (52) hide show
  1. package/README.md +13 -5
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +1 -1
  4. package/dist/constants.d.ts +2 -0
  5. package/dist/constants.d.ts.map +1 -1
  6. package/dist/constants.js +10 -2
  7. package/dist/display.d.ts +9 -0
  8. package/dist/display.d.ts.map +1 -1
  9. package/dist/display.js +19 -1
  10. package/dist/file-system.d.ts +21 -4
  11. package/dist/file-system.d.ts.map +1 -1
  12. package/dist/file-system.js +52 -6
  13. package/dist/library-range.d.ts +16 -0
  14. package/dist/library-range.d.ts.map +1 -0
  15. package/dist/library-range.js +40 -0
  16. package/dist/project-creator.d.ts +16 -1
  17. package/dist/project-creator.d.ts.map +1 -1
  18. package/dist/project-creator.js +39 -7
  19. package/dist/prompts.d.ts +1 -0
  20. package/dist/prompts.d.ts.map +1 -1
  21. package/dist/prompts.js +3 -2
  22. package/dist/validators.d.ts +8 -0
  23. package/dist/validators.d.ts.map +1 -1
  24. package/dist/validators.js +18 -1
  25. package/package.json +8 -8
  26. package/templates/skills/bestax-custom-component/references/component-catalog.md +5 -1
  27. package/templates/skills/bestax-form/SKILL.md +45 -16
  28. package/templates/skills/bestax-form/references/api.md +13 -8
  29. package/templates/skills/bestax-form/references/patterns.md +75 -3
  30. package/templates/skills/bestax-layout-scaffold/SKILL.md +34 -6
  31. package/templates/skills/bestax-layout-scaffold/references/archetypes.md +2 -2
  32. package/templates/skills/bestax-layout-scaffold/references/layout-components.md +52 -5
  33. package/templates/skills/bestax-migrate/SKILL.md +34 -5
  34. package/templates/skills/bestax-migrate/references/bloomer/component-map.md +1 -1
  35. package/templates/skills/bestax-migrate/references/bloomer/prop-map.md +12 -9
  36. package/templates/skills/bestax-migrate/references/bulma-classes/component-map.md +153 -105
  37. package/templates/skills/bestax-migrate/references/bulma-classes/prop-map.md +48 -17
  38. package/templates/skills/bestax-migrate/references/bulma-classes/unmappables.md +501 -16
  39. package/templates/skills/bestax-migrate/references/css-migration.md +12 -1
  40. package/templates/skills/bestax-migrate/references/rbx/prop-map.md +23 -17
  41. package/templates/skills/bestax-migrate/references/react-bulma-components/prop-map.md +27 -22
  42. package/templates/skills/bestax-migrate/references/react-bulma-components/unmappables.md +5 -5
  43. package/templates/skills/bestax-optimize/references/modular-build.md +57 -15
  44. package/templates/skills/bestax-theming/SKILL.md +19 -8
  45. package/templates/skills/bestax-theming/examples/dark-mode.tsx +99 -17
  46. package/templates/skills/bestax-theming/examples/theme-config.tsx +13 -8
  47. package/templates/skills/bestax-theming/references/css-variables.md +231 -18
  48. package/templates/skills/bestax-theming/references/themeable-components.md +4 -0
  49. package/templates/vite/package.json +1 -1
  50. package/templates/vite/src/main.jsx +1 -1
  51. package/templates/vite-ts/package.json +1 -1
  52. package/templates/vite-ts/src/main.tsx +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bestax",
3
- "version": "4.2.17",
3
+ "version": "4.2.19",
4
4
  "description": "Create a new bestax-bulma project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,26 +34,26 @@
34
34
  "url": "https://github.com/allxsmith/bestax/issues"
35
35
  },
36
36
  "dependencies": {
37
- "chalk": "^6.0.0",
37
+ "chalk": "^6.0.1",
38
38
  "commander": "^15.0.0",
39
39
  "figures": "^6.1.0",
40
- "fs-extra": "^11.4.0",
40
+ "fs-extra": "^11.4.1",
41
41
  "prompts": "^2.4.2",
42
42
  "react": "^18.0.0 || ^19.0.0",
43
43
  "react-dom": "^18.0.0 || ^19.0.0",
44
- "@allxsmith/bestax-bulma": "^5.34.3"
44
+ "@allxsmith/bestax-bulma": "^5.44.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@jest/globals": "^30.0.0",
48
- "@playwright/test": "^1.63.0",
48
+ "@playwright/test": "^1.64.1",
49
49
  "@types/fs-extra": "^11.0.4",
50
50
  "@types/jest": "^30.0.0",
51
- "@types/node": "^26.6.4",
51
+ "@types/node": "^26.6.5",
52
52
  "@types/prompts": "^2.4.9",
53
53
  "@types/wait-on": "^5.3.4",
54
54
  "jest": "^30.2.0",
55
55
  "semver": "^7.8.5",
56
- "ts-jest": "^29.4.14",
56
+ "ts-jest": "^29.4.15",
57
57
  "typescript": "^6.0.3",
58
58
  "wait-on": "^9.5.1"
59
59
  },
@@ -78,5 +78,5 @@
78
78
  "clean": "rimraf dist",
79
79
  "release": "npx semantic-release"
80
80
  },
81
- "readme": "# create-bestax\n\n[![npm version](https://img.shields.io/npm/v/create-bestax.svg)](https://www.npmjs.com/package/create-bestax)\n[![npm downloads](https://img.shields.io/npm/dm/create-bestax.svg)](https://www.npmjs.com/package/create-bestax)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Socket Badge](https://badge.socket.dev/npm/package/create-bestax/latest)](https://socket.dev/npm/package/create-bestax/overview)\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/allxsmith/bestax/badge)](https://scorecard.dev/viewer/?uri=github.com/allxsmith/bestax)\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/14361/badge)](https://www.bestpractices.dev/projects/14361)\n[![npm provenance](https://img.shields.io/badge/npm-provenance-3fb950.svg)](https://www.npmjs.com/package/create-bestax#provenance)\n[![Security policy](https://img.shields.io/badge/security-policy-blue.svg)](https://github.com/allxsmith/bestax/blob/main/SECURITY.md)\n\nThe scaffolder for [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma) — spin up a Vite app pre-wired for the **Bulma v1** React component library in one command. Picks your framework (JS or TypeScript), CSS flavor, and icon library, and can drop in the bestax **AI skills** so an agent like Claude Code knows the library from the first prompt.\n\nPart of the [bestax monorepo](https://github.com/allxsmith/bestax) — see also [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma) for the components themselves.\n\n## Requirements\n\n- **Node.js 22 or newer.** Node 18 and 20 are both past end-of-life. On an older runtime the\n CLI exits immediately with an explicit upgrade message.\n- npm 10 or newer (ships with Node 22), or yarn/pnpm.\n\n## Usage\n\n### Quick Start\n\n```bash\n# Using npm\nnpm create bestax@latest my-app\n\n# Using npx\nnpx create-bestax@latest my-app\n```\n\n### Interactive Mode\n\nRunning without arguments will prompt you for all options:\n\n```bash\nnpm create bestax@latest\n```\n\nYou'll be asked to:\n\n1. Enter a project name (and, if that directory already holds files, confirm removing them;\n nothing is removed until you have answered the last question)\n2. Select a framework (Vite or Vite + TypeScript)\n3. Choose a Bulma CSS flavor (the `--bulma` options below)\n4. Select an icon library (Font Awesome, Material Icons, etc.)\n5. Choose whether to install the bestax AI skills into `.claude/skills/`\n\nEvery prompt has a flag equivalent, so the whole flow is scriptable and CI-friendly — see the options below.\n\n### Command Line Options\n\n```bash\nnpm create bestax@latest [project-directory] -- [options]\n```\n\nPut the options after `--`. npm needs it to pass them on at all, and so does bun, whose\n`bun create` rejects short flags such as `-t` and `-y` itself. pnpm and Yarn 1 accept the options\nwith or without it.\n\n**Arguments:**\n\n- `[project-directory]` - Optional project directory name. It also becomes the `package.json`\n name, lower-cased the way `npm init` does and without a leading `_`, since npm rejects both.\n\n**Options:**\n\n- `-t, --template <template>` - Template to use: `vite` or `vite-ts` \n **Default:** `vite`\n- `-b, --bulma <flavor>` - Bulma CSS flavor:\n - `complete` - Full Bulma CSS with all components and helpers\n - `prefixed` - Prefixed version for compatibility\n - `no-helpers` - Without helper classes\n - `no-helpers-prefixed` - Prefixed without helpers\n - `no-dark-mode` - Without dark mode support \n **Default:** `complete`\n- `-i, --icon <library>` - Icon library:\n - `none` - No icon library\n - `fontawesome` - Font Awesome\n - `mdi` - Material Design Icons\n - `ionicons` - Ionicons\n - `material-icons` - Material Icons\n - `material-symbols` - Material Symbols \n **Default:** `none`\n- `--skills` / `--no-skills` - Install (or skip) the bestax AI skills into\n `.claude/skills/` (with a `CLAUDE.md`) so a Claude Code session picks them up\n automatically. When omitted you're prompted; with `-y` the default is to install.\n- `-y, --yes` - Skip prompts and use defaults or provided options. \n When used, the following defaults are selected unless overridden by flags:\n - Template: `vite`\n - Bulma flavor: `complete`\n - Icon library: `none`\n - AI skills: installed (pass `--no-skills` to opt out)\n\n `-y` never deletes files. If the target directory is not empty, the run stops, lists what is\n there, and changes nothing; add `--overwrite` to empty it first. Earlier versions emptied the\n directory under `-y` alone, so a script that relied on that now needs `--overwrite`.\n\n- `--overwrite` - Remove existing files if the target directory is not empty, without asking.\n Every other flag is checked first, so a typo stops the run before anything is deleted.\n\n**Example:**\n\n```bash\n# Create a TypeScript project with Font Awesome icons\nnpm create bestax@latest my-app -- -t vite-ts -i fontawesome -b complete\n\n# Use all defaults (skip prompts)\nnpm create bestax@latest my-app -- -y\n\n# The same with bun\nbun create bestax@latest my-app -- -y\n```\n\n## Templates\n\nAvailable templates:\n\n- `vite` - Vite + JavaScript\n- `vite-ts` - Vite + TypeScript\n\nEach template includes:\n\n- Pre-configured bestax-bulma integration (React 19)\n- The latest `@allxsmith/bestax-bulma`, which ships Bulma v1 automatically\n- Icon library support (Font Awesome, Material Design, etc.)\n- Sample components demonstrating library usage\n- Development and build scripts\n\n## For AI Tools\n\nScaffolding for an AI agent? Pass `--skills` (or accept the prompt) to drop the bestax\nAgent Skills and a `CLAUDE.md` into the new project so Claude Code, Cursor, or Copilot\nbuild the bestax way from the start. The library also ships LLM-optimized docs:\n\n- [LLMs guide](https://bestax.io/docs/guides/llms) — using bestax with Claude Code, Cursor, Copilot\n- [Agent Skills](https://bestax.io/docs/skills/intro) — teach your agent the bestax conventions\n- [llms.txt](https://bestax.io/llms.txt) (curated index) · [llms-full.txt](https://bestax.io/llms-full.txt) (full docs)\n\n## Development\n\n```bash\nnpm run build # Build the CLI\nnpm run dev # Watch mode\nnpm test # Run tests\nnpm run lint # Lint CLI source code\nnpm run typecheck # Type check CLI source code\n```\n\n**Note on Templates:** Template files in `templates/` are excluded from linting. They should be manually validated by scaffolding a test project and running lint/build there before releasing.\n\n## Hardened by default\n\nA scaffolder runs with write access to your filesystem and picks your starting dependencies, so how it is built and published matters:\n\n- **Signed provenance** — every release carries a sigstore attestation linking the tarball to the exact commit and CI run that built it. Check the **Provenance** section on the [npm page](https://www.npmjs.com/package/create-bestax#provenance), or run `npm audit signatures`.\n- **npm OIDC trusted publishing** — short-lived, per-run credentials; no long-lived `NPM_TOKEN` exists to be stolen. Release commits and tags are GPG-signed.\n- **Socket.dev scans every PR** for malware, install scripts, obfuscated code, and privilege escalation before it can reach `main`.\n- **Dependencies are a deliberate act** — install scripts are blocked unless individually allow-listed, freshly published versions are refused for 3 days, and CI installs only what the reviewed lockfile resolves.\n- **Every GitHub Action is pinned to a full commit SHA**, so a compromised action release can't roll silently into a build of this CLI.\n- **CodeQL, Dependency Review, and Dependabot** run continuously, alongside a high-severity `pnpm audit` gate.\n- **Layered AI review before merge** — [CodeRabbit](https://coderabbit.ai) plus an independent adversarial Claude review (a different model from the one writing AI-authored changes), on top of required green CI, an approving review, and a human merge.\n\nFull detail: [`SECURITY.md`](https://github.com/allxsmith/bestax/blob/main/SECURITY.md) · [Security guide](https://bestax.io/docs/guides/security)\n\n## Telemetry\n\n`create-bestax` can send one **anonymous** usage event after a successful\nscaffold — only if you opt in when asked (once, at the end of a scaffold). The\nevent is just the choices you made — template, Bulma flavor, icon library,\nskills, package manager — plus the CLI version, Node major version, and OS\nplatform; never names, IPs, machine IDs, paths, or file contents, and no\nidentifier exists that could link two events together. Opt out any time with\n`--no-telemetry`, `BESTAX_TELEMETRY=0`, or `DO_NOT_TRACK=1`.\n\nFull disclosure of every field and control: [Telemetry guide](https://bestax.io/docs/guides/telemetry)\n\n## Publishing\n\nThis package uses semantic-release with scope-based rules. Only commits with `feat(create-bestax)` or `fix(create-bestax)` will trigger releases.\n"
81
+ "readme": "# create-bestax\n\n[![npm version](https://img.shields.io/npm/v/create-bestax.svg)](https://www.npmjs.com/package/create-bestax)\n[![npm downloads](https://img.shields.io/npm/dm/create-bestax.svg)](https://www.npmjs.com/package/create-bestax)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Socket Badge](https://badge.socket.dev/npm/package/create-bestax/latest)](https://socket.dev/npm/package/create-bestax/overview)\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/allxsmith/bestax/badge)](https://scorecard.dev/viewer/?uri=github.com/allxsmith/bestax)\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/14361/badge)](https://www.bestpractices.dev/projects/14361)\n[![npm provenance](https://img.shields.io/badge/npm-provenance-3fb950.svg)](https://www.npmjs.com/package/create-bestax#provenance)\n[![Security policy](https://img.shields.io/badge/security-policy-blue.svg)](https://github.com/allxsmith/bestax/blob/main/SECURITY.md)\n\nThe scaffolder for [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma) — spin up a Vite app pre-wired for the **Bulma v1** React component library in one command. Picks your framework (JS or TypeScript), CSS flavor, and icon library, and can drop in the bestax **AI skills** so an agent like Claude Code knows the library from the first prompt.\n\nPart of the [bestax monorepo](https://github.com/allxsmith/bestax) — see also [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma) for the components themselves.\n\n## Requirements\n\n- **Node.js 22 or newer.** Node 18 and 20 are both past end-of-life. On an older runtime the\n CLI exits immediately with an explicit upgrade message.\n- npm 10 or newer (ships with Node 22), or yarn/pnpm.\n\n## Usage\n\n### Quick Start\n\n```bash\n# Using npm\nnpm create bestax@latest my-app\n\n# Using npx\nnpx create-bestax@latest my-app\n```\n\n### Interactive Mode\n\nRunning without arguments will prompt you for all options:\n\n```bash\nnpm create bestax@latest\n```\n\nYou'll be asked to:\n\n1. Enter a project name or path (and, if that directory already holds files, confirm removing\n them; nothing is removed until you have answered the last question)\n2. Select a framework (Vite or Vite + TypeScript)\n3. Choose a Bulma CSS flavor (the `--bulma` options below)\n4. Select an icon library (Font Awesome, Material Icons, etc.)\n5. Choose whether to install the bestax AI skills into `.claude/skills/`\n\nEvery prompt has a flag equivalent, so the whole flow is scriptable and CI-friendly — see the options below.\n\n### Command Line Options\n\n```bash\nnpm create bestax@latest [project-directory] -- [options]\n```\n\nPut the options after `--`. npm needs it to pass them on at all, and so does bun, whose\n`bun create` rejects short flags such as `-t` and `-y` itself. pnpm and Yarn 1 accept the options\nwith or without it.\n\n**Arguments:**\n\n- `[project-directory]` - Optional project directory: a name such as `my-app`, or a path,\n absolute or relative, such as `../apps/my-app`, whose missing folders are created. Its last\n part is the project name, held to the same rules as a bare name. It becomes the page title as\n typed, and the `package.json` name lower-cased the way `npm init` does and without a leading\n `_`, since npm rejects both. A path ending in `.` or `..` is refused, like a bare `.` or `..`,\n so name the directory you want.\n\n**Options:**\n\n- `-t, --template <template>` - Template to use: `vite` or `vite-ts` \n **Default:** `vite`\n- `-b, --bulma <flavor>` - Bulma CSS flavor:\n - `complete` - Full Bulma CSS with all components and helpers\n - `prefixed` - Prefixed version for compatibility\n - `no-helpers` - Without helper classes\n - `no-helpers-prefixed` - Prefixed without helpers\n - `no-dark-mode` - Without dark mode support \n **Default:** `complete`\n- `-i, --icon <library>` - Icon library:\n - `none` - No icon library\n - `fontawesome` - Font Awesome\n - `mdi` - Material Design Icons\n - `ionicons` - Ionicons\n - `material-icons` - Material Icons\n - `material-symbols` - Material Symbols \n **Default:** `none`\n- `--skills` / `--no-skills` - Install (or skip) the bestax AI skills into\n `.claude/skills/` (with a `CLAUDE.md`) so a Claude Code session picks them up\n automatically. When omitted you're prompted; with `-y` the default is to install.\n- `-y, --yes` - Skip prompts and use defaults or provided options. \n When used, the following defaults are selected unless overridden by flags:\n - Template: `vite`\n - Bulma flavor: `complete`\n - Icon library: `none`\n - AI skills: installed (pass `--no-skills` to opt out)\n\n `-y` never deletes files. If the target directory is not empty, the run stops, lists what is\n there, and changes nothing; add `--overwrite` to empty it first. Earlier versions emptied the\n directory under `-y` alone, so a script that relied on that now needs `--overwrite`.\n\n- `--overwrite` - Remove existing files if the target directory is not empty, without asking.\n Every other flag is checked first, so a typo stops the run before anything is deleted. It\n never empties the current directory or a folder that holds it, however the path is written.\n\n**Example:**\n\n```bash\n# Create a TypeScript project with Font Awesome icons\nnpm create bestax@latest my-app -- -t vite-ts -i fontawesome -b complete\n\n# Use all defaults (skip prompts)\nnpm create bestax@latest my-app -- -y\n\n# Scaffold into a path; the package is named my-app\nnpm create bestax@latest ../apps/my-app -- -y\n\n# The same with bun\nbun create bestax@latest my-app -- -y\n```\n\n## Templates\n\nAvailable templates:\n\n- `vite` - Vite + JavaScript\n- `vite-ts` - Vite + TypeScript\n\nEach template includes:\n\n- Pre-configured bestax-bulma integration (React 19)\n- The latest `@allxsmith/bestax-bulma`, which ships Bulma v1 automatically\n- Icon library support (Font Awesome, Material Design, etc.)\n- Sample components demonstrating library usage\n- Development and build scripts\n\n## For AI Tools\n\nScaffolding for an AI agent? Pass `--skills` (or accept the prompt) to drop the bestax\nAgent Skills and a `CLAUDE.md` into the new project so Claude Code, Cursor, or Copilot\nbuild the bestax way from the start. The library also ships LLM-optimized docs:\n\n- [LLMs guide](https://bestax.io/docs/guides/llms) — using bestax with Claude Code, Cursor, Copilot\n- [Agent Skills](https://bestax.io/docs/skills/intro) — teach your agent the bestax conventions\n- [llms.txt](https://bestax.io/llms.txt) (curated index) · [llms-full.txt](https://bestax.io/llms-full.txt) (full docs)\n\n## Development\n\n```bash\nnpm run build # Build the CLI\nnpm run dev # Watch mode\nnpm test # Run tests\nnpm run lint # Lint CLI source code\nnpm run typecheck # Type check CLI source code\n```\n\n**Note on Templates:** Template files in `templates/` are excluded from linting. They should be manually validated by scaffolding a test project and running lint/build there before releasing.\n\n## Hardened by default\n\nA scaffolder runs with write access to your filesystem and picks your starting dependencies, so how it is built and published matters:\n\n- **Signed provenance** — every release carries a sigstore attestation linking the tarball to the exact commit and CI run that built it. Check the **Provenance** section on the [npm page](https://www.npmjs.com/package/create-bestax#provenance), or run `npm audit signatures`.\n- **npm OIDC trusted publishing** — short-lived, per-run credentials; no long-lived `NPM_TOKEN` exists to be stolen. Release commits and tags are GPG-signed.\n- **Socket.dev scans every PR** for malware, install scripts, obfuscated code, and privilege escalation before it can reach `main`.\n- **Dependencies are a deliberate act** — install scripts are blocked unless individually allow-listed, freshly published versions are refused for 3 days, and CI installs only what the reviewed lockfile resolves.\n- **Every GitHub Action is pinned to a full commit SHA**, so a compromised action release can't roll silently into a build of this CLI.\n- **CodeQL, Dependency Review, and Dependabot** run continuously, alongside a high-severity `pnpm audit` gate.\n- **Layered AI review before merge** — [CodeRabbit](https://coderabbit.ai) plus an independent adversarial Claude review (a different model from the one writing AI-authored changes), on top of required green CI, an approving review, and a human merge.\n\nFull detail: [`SECURITY.md`](https://github.com/allxsmith/bestax/blob/main/SECURITY.md) · [Security guide](https://bestax.io/docs/guides/security)\n\n## Telemetry\n\n`create-bestax` can send one **anonymous** usage event after a successful\nscaffold — only if you opt in when asked (once, at the end of a scaffold). The\nevent is just the choices you made — template, Bulma flavor, icon library,\nskills, package manager — plus the CLI version, Node major version, and OS\nplatform; never names, IPs, machine IDs, paths, or file contents, and no\nidentifier exists that could link two events together. Opt out any time with\n`--no-telemetry`, `BESTAX_TELEMETRY=0`, or `DO_NOT_TRACK=1`.\n\nFull disclosure of every field and control: [Telemetry guide](https://bestax.io/docs/guides/telemetry)\n\n## Publishing\n\nThis package uses semantic-release with scope-based rules. Only commits with `feat(create-bestax)` or `fix(create-bestax)` will trigger releases.\n"
82
82
  }
@@ -37,7 +37,7 @@ instead of hand-writing markup.
37
37
  - Raw `*Base` form exports (`InputBase`, `SelectBase`, `TextAreaBase`, …) are
38
38
  escape-hatch variants of the convenience wrappers above them; see the Form docs.
39
39
 
40
- 100 documented components. Generated from the API docs — every exported
40
+ 104 documented components. Generated from the API docs — every exported
41
41
  component is guaranteed to appear (the generator fails if one lacks an API page).
42
42
 
43
43
  ## Elements
@@ -104,14 +104,17 @@ component is guaranteed to appear (the generator fails if one lacks an API page)
104
104
  - [Sidebar](https://bestax.io/docs/api/components/sidebar) — The `Sidebar` component provides a slide-out navigation panel that appears from the left or right side of the screen.
105
105
  - [Steps](https://bestax.io/docs/api/components/steps) — The `Steps` component provides a multi-step progress indicator for wizard flows, checkout processes, or any multi-step workflow.
106
106
  - [Tabs](https://bestax.io/docs/api/components/tabs) — The `Tabs` component provides flexible and fully-featured Bulma tab navigation for your Bulma React UI.
107
+ - [Timeline](https://bestax.io/docs/api/components/timeline) — The `Timeline` component renders a list of events in the order they happened, each with a marker on a line down the side.
107
108
  - [Toast](https://bestax.io/docs/api/components/toast) — The `Toast` component provides brief notification messages with optional action and cancel buttons.
108
109
  - [Tooltip](https://bestax.io/docs/api/components/tooltip) — The `Tooltip` component displays helpful information when users hover over or focus on an element.
110
+ - [TreeView](https://bestax.io/docs/api/components/treeview) — The `TreeView` component renders a tree of items you browse and pick from, the way a file explorer works: one tab stop, the arrow keys to move…
109
111
 
110
112
  ## Form
111
113
 
112
114
  - [Autocomplete](https://bestax.io/docs/api/form/autocomplete) — The `Autocomplete` component provides an input field with dropdown suggestions that filter based on user input.
113
115
  - [Checkbox](https://bestax.io/docs/api/form/checkbox) — The `Checkbox` component provides a Bulma-styled checkbox input.
114
116
  - [Checkboxes](https://bestax.io/docs/api/form/checkboxes) — The `Checkboxes` component wraps multiple `Checkbox` components in a Bulma-styled group.
117
+ - [Combobox](https://bestax.io/docs/api/form/combobox) — The `Combobox` component is a text field with a list to pick from under it, for a searchable select, a multi-select with tags, grouped items or a server search…
115
118
  - [Control](https://bestax.io/docs/api/form/control) — The `Control` component is a Bulma-styled wrapper for form controls (`Input`, `Select`, `TextArea`, etc.), supporting icons (left/right), loading state…
116
119
  - [DateInput](https://bestax.io/docs/api/form/datetime/dateinput) — The `DateInput` component is a form input that opens a popover calendar for date selection.
117
120
  - [DateRangeInput](https://bestax.io/docs/api/form/datetime/daterangeinput) — The `DateRangeInput` component is a form input for a start and end date, picked from one popover calendar or typed into two segmented inputs.
@@ -120,6 +123,7 @@ component is guaranteed to appear (the generator fails if one lacks an API page)
120
123
  - [File](https://bestax.io/docs/api/form/file) — The `File` component provides a Bulma-styled file input, supporting color, size, boxed/fullwidth/align styles, icons, "has name", and filename display.
121
124
  - [Input](https://bestax.io/docs/api/form/input) — The `Input` component provides a Bulma-styled text input, supporting colors, sizes, rounded corners, static state, hover/focus/loading states…
122
125
  - [Numberinput](https://bestax.io/docs/api/form/numberinput) — The `Numberinput` component provides a number input with increment/decrement buttons.
126
+ - [PinInput](https://bestax.io/docs/api/form/pininput) — The `PinInput` component is a field for a short code, such as a one-time code or a PIN, drawn as a row of boxes with one character in each.
123
127
  - [Radio](https://bestax.io/docs/api/form/radio) — The `Radio` component provides a Bulma-styled radio button input with flexible labels and helper classes.
124
128
  - [Radios](https://bestax.io/docs/api/form/radios) — The `Radios` component wraps multiple `Radio` components in a Bulma-styled group.
125
129
  - [Rate](https://bestax.io/docs/api/form/rate) — The `Rate` component provides a star/icon-based rating system.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: bestax-form
3
- description: Build forms with @allxsmith/bestax-bulma — Field/Control/Label/Help composition, inputs, selects, checkboxes, radios, switches, and advanced controls (Autocomplete, Slider, Numberinput, Rate, Taginput, File, date/time). There is no form/validation library; this skill shows the components and the validate-it-yourself error pattern. Use when building a form, wiring inputs to state, or showing validation/error state.
3
+ description: Build forms with @allxsmith/bestax-bulma — Field/Control/Label/Help composition, inputs, selects, checkboxes, radios, switches, and advanced controls (Autocomplete, Combobox, Slider, Numberinput, PinInput, Rate, Taginput, File, date/time). There is no form/validation library; this skill shows the components and the validate-it-yourself error pattern. Use when building a form, wiring inputs to state, or showing validation/error state.
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -65,7 +65,7 @@ using context (`useInsideField` / `useInsideControl`) to detect their surroundin
65
65
  ```tsx
66
66
  <Field hasAddons>
67
67
  <Control isExpanded>
68
- <InputBase placeholder="Search" />
68
+ <InputBase placeholder="Search" aria-label="Search" />
69
69
  </Control>
70
70
  <Control>
71
71
  <Button color="primary">Go</Button>
@@ -73,6 +73,10 @@ using context (`useInsideField` / `useInsideControl`) to detect their surroundin
73
73
  </Field>
74
74
  ```
75
75
 
76
+ A `grouped` or `hasAddons` Field names none of its controls, and a placeholder disappears once
77
+ the user types, so give each input in the row an `aria-label`, or a visible label on an outer
78
+ `Field` (`references/patterns.md`, "Grouped controls and addons").
79
+
76
80
  ## Component inventory
77
81
 
78
82
  All import from `@allxsmith/bestax-bulma`. Convenience components auto-wrap Field+Control;
@@ -89,9 +93,11 @@ All import from `@allxsmith/bestax-bulma`. Convenience components auto-wrap Fiel
89
93
  | `Radio` / `Radios` | Single radio / managed single-select group. |
90
94
  | `Switch` | Toggle switch (`isRounded`, `isThin`, `isOutlined`, RTL). |
91
95
  | `File` | File upload input with label/message. |
92
- | `Autocomplete` | Input with filtered dropdown suggestions + keyboard nav. |
96
+ | `Autocomplete` | Free text with suggestions it filters for you, + keyboard nav. |
97
+ | `Combobox` | A value from a list: `data` for a select, tags or groups, or parts. |
93
98
  | `Slider` | Range slider; single/dual thumbs, steps, tooltips, vertical. |
94
99
  | `Numberinput` | Numeric input with increment/decrement, min/max, step, stepper. |
100
+ | `PinInput` | A one-time code or PIN as a row of boxes, all in one real input. |
95
101
  | `Rate` | Star rating; `max`, `precision` (half/quarter), custom icons, `disabled`. |
96
102
  | `Taginput` | Tag/chip input; suggestions, confirm keys, closable tags. |
97
103
  | `DateInput` / `TimeInput` / `DateTimeInput` (+ `*Base`) | Date / time / datetime pickers; month or year via `granularity`. |
@@ -100,6 +106,27 @@ All import from `@allxsmith/bestax-bulma`. Convenience components auto-wrap Fiel
100
106
  (`NumberInput` and `TagInput` also exist as deprecated aliases of `Numberinput`/`Taginput` —
101
107
  same components; prefer the lowercase-second-word spellings.)
102
108
 
109
+ **Autocomplete or Combobox?** A field whose text is the value, with suggestions to help type it,
110
+ is `Autocomplete`, which filters its `data` itself. A value picked from a list is `Combobox`. Given
111
+ `data`, it filters by label, keeps the value (`value`/`onChange`) and shows the picks as tags with
112
+ `multiple`, so a searchable select, a multi-select or a grouped list is one element. Build it from
113
+ parts for a server search whose answer must show unfiltered, or a "Create" item: then it keeps no
114
+ value and never sets the field's text, so you filter the items, keep the value and set the text in
115
+ `onSelect`.
116
+
117
+ **A verification code or a PIN is `PinInput`**, not a row of one-character `Input`s with focus
118
+ moved by hand. It holds the code in one real input under a row of boxes, so a screen reader hears
119
+ one field named by its `label`, and paste, autofill from a text message (`autoComplete` is
120
+ `"one-time-code"`) and password managers work as they do on any input. Give it `length` (6 by
121
+ default), `value`/`onChange` with the code as a string, and verify in `onComplete`, which fires
122
+ once each time a change fills the last box. It takes digits unless `type="alphanumeric"`, turns a
123
+ digit from any script (Arabic-Indic, Persian) into its ASCII digit, drops anything else typed or
124
+ pasted, and `isMasked` shows dots for a PIN. A paste replaces the whole code. Its default `pattern`
125
+ asks for the full length, so `required` rejects a partial code, not just an empty one. Say the
126
+ length in the `label` ("Enter the 6-digit code"): the boxes are hidden from a screen reader, and the
127
+ `message` isn't tied to the input, so it isn't read on focus. Separate help text needs
128
+ `aria-describedby` on the PinInput.
129
+
103
130
  ## Common props
104
131
 
105
132
  Across the convenience inputs (`Input`, `Select`, `TextArea`, and similar):
@@ -126,13 +153,15 @@ spellings compile only where they historically existed: `isFullWidth` everywhere
126
153
  `Sidebar`, `fullwidth` on `Tabs` only, `fullWidth` on `Sidebar` only.
127
154
 
128
155
  **Labels.** Default to the convenience `label` prop, with `horizontal` for a side-by-side row.
129
- `Input`, `Select`, `TextArea`, `File`, `Numberinput`, `Slider`, `DateInput`, `TimeInput`,
130
- `DateTimeInput`, `Autocomplete` and `Taginput` then render the `Field` and the label and wire
131
- `htmlFor`/`id`, to your `id` when you pass one (`<Input label="Email" id="email" horizontal />`);
132
- an explicit `labelProps={{ htmlFor }}` wins. The group inputs (`Checkboxes`, `Radios`, `Rate`,
133
- `DateRangeInput`) name their `role="group"`/`"radiogroup"` wrapper through `aria-labelledby`
134
- instead. Inside a `Field` a convenience input drops its `label`, and an `inline` date/time picker
135
- or a `Taginput` at `maxTags` has no visible input to wire.
156
+ `Input`, `Select`, `TextArea`, `File`, `Numberinput`, `PinInput`, `Slider`, `DateInput`,
157
+ `TimeInput`, `DateTimeInput`, `Autocomplete` and `Taginput` then render the `Field` and the label
158
+ and wire `htmlFor`/`id`, to your `id` when you pass one
159
+ (`<Input label="Email" id="email" horizontal />`); an explicit `labelProps={{ htmlFor }}` wins. `Combobox` renders its `Field` and label too, and wires
160
+ them to its field. Given `data`, its `id`, `aria-label` and `aria-labelledby` go to the field;
161
+ built from parts, put them on `Combobox.Input`, since on the `Combobox` they land on its wrapper
162
+ `div`. The group inputs (`Checkboxes`, `Radios`, `Rate`, `DateRangeInput`) name their
163
+ `role="group"`/`"radiogroup"` wrapper through `aria-labelledby` instead. Inside a `Field` a
164
+ convenience input drops its `label`, and an `inline` date/time picker has no visible input to wire.
136
165
 
137
166
  Composed, a labeled `Field` names the one control it holds: a composed `InputBase`/`SelectBase`/
138
167
  `TextAreaBase`, a composed `DateInputBase`/`TimeInputBase`/`DateTimeInputBase` that is not
@@ -148,9 +177,9 @@ unmatched `for` out of server-rendered HTML.
148
177
 
149
178
  A `range` `Slider` points the `for` at its low thumb and starts both thumbs' names with the label
150
179
  ("Price range Minimum value"); its `ariaLabel={[low, high]}` replaces those names outright, so
151
- leave it off when a label already names the Slider. Those thumbs and an `Autocomplete`'s or
152
- `Taginput`'s suggestion list point `aria-labelledby` at the label's `id`, which a label wired by
153
- hand has only when you pass one, `labelProps={{ htmlFor, id }}`; without it they keep their
180
+ leave it off when a label already names the Slider. Those thumbs and an `Autocomplete`'s, a
181
+ `Combobox`'s or a `Taginput`'s list point `aria-labelledby` at the label's `id`, which a label
182
+ wired by hand has only when you pass one, `labelProps={{ htmlFor, id }}`; without it they keep their
154
183
  fallback names ("Minimum value"/"Maximum value", "Suggestions"). A `<label htmlFor>` you put in
155
184
  `Field.Label` yourself (`references/patterns.md`) names its control through the `for` alone, so
156
185
  label a row holding either with the `Field`'s `label` prop. An `aria-label` or `aria-labelledby`
@@ -239,9 +268,9 @@ multi-field form plus the advanced inputs.
239
268
 
240
269
  ## Reuse the shipped components
241
270
 
242
- bestax ships the whole form surface — Input, Select, TextArea, Checkbox(es), Radio(s), Switch,
243
- File, Autocomplete, Slider, Numberinput, Rate, Taginput, and the date/time inputs (see the
244
- inventory above). **Compose these; don't hand-roll raw `<input class="input">` markup or
271
+ bestax ships the whole form surface — Input, Select, TextArea, Checkbox(es), Radio(s), Switch, File,
272
+ Autocomplete, Combobox, Slider, Numberinput, PinInput, Rate, Taginput, and the date/time inputs (see
273
+ the inventory above). **Compose these; don't hand-roll raw `<input class="input">` markup or
245
274
  reinvent a control.** If you think a control is missing, check `bulma-ui/src/index.ts` and
246
275
  `docs/docs/api/form/` first — it's probably already there under a different name.
247
276
 
@@ -19,7 +19,7 @@ Container and layout. Compound parts: `Field.Label`, `Field.Body`.
19
19
  | `labelProps` | label attributes | Props for the `<label>`. An explicit `htmlFor` (even `undefined`) takes over the association; otherwise it is set automatically. For its `id`, see below. |
20
20
  | `textColor` / `bgColor` | Bulma color | Helper colors for the field. |
21
21
 
22
- An `id` in `labelProps` is what a group's, a range `Slider`'s and an `Autocomplete` or `Taginput` suggestion list's `aria-labelledby` points at. A label wired by hand with `htmlFor` gets no generated id, so give it one, `labelProps={{ htmlFor, id }}`, for a range `Slider`, an `Autocomplete` or a `Taginput`. No group points at a label wired by hand.
22
+ An `id` in `labelProps` is what a group's, a range `Slider`'s and an `Autocomplete`'s, a `Combobox`'s or a `Taginput`'s list's `aria-labelledby` points at. A label wired by hand with `htmlFor` gets no generated id, so give it one, `labelProps={{ htmlFor, id }}`, for a range `Slider`, an `Autocomplete`, a `Combobox` or a `Taginput`. No group points at a label wired by hand.
23
23
 
24
24
  ## Control — `form/Control.tsx`
25
25
 
@@ -70,6 +70,9 @@ Field/Control/message props. `TextArea` adds `rows` and `hasFixedSize`.
70
70
 
71
71
  - `Checkbox` / `Radio` — single controls with `color`, `size`, `checked`/`defaultChecked`,
72
72
  `onChange`, `disabled`.
73
+ - `Checkbox` also takes `indeterminate`, the mixed state of a "Select all" box over a partly
74
+ checked list. It changes the look and the announcement, not the value: `checked` stays the
75
+ value, so set both from the list.
73
76
  - `Checkboxes` — group wrapper managing an **array** value.
74
77
  - `Radios` — group wrapper managing a **single** selected value.
75
78
 
@@ -84,13 +87,15 @@ File input with `label`, `message`, color/size, and icon support.
84
87
 
85
88
  ## Advanced inputs
86
89
 
87
- | Component | Key props |
88
- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
89
- | `Autocomplete` | `data` (`string[]` or item objects), `value`, `onInput(value)` for typing, `onSelect(item)` for picks, `clearable` (the clear button hides while `loading`, or a loading `Control` around it, puts a spinner at the same edge), `openOnFocus`, `loading`. |
90
- | `Slider` | `min`, `max`, `step`, single or dual thumb (`value` number or `[number, number]`), `tooltip` (`'auto' \| 'always' \| 'hidden'`), vertical orientation, `color`, `size`. |
91
- | `Numberinput` | `value`/`onChange`, `min`, `max`, `step`, increment/decrement buttons, stepper variant, `color`, `size`, `isLoading` (set it here, not on a `Control` around it, whose spinner covers the right-hand button). |
92
- | `Rate` | `value`/`onChange`, `max`, `precision` (1 / 0.5 / 0.25), `customIcon` or `iconName`, `showScore`/`showText`/`texts`, `disabled`. |
93
- | `Taginput` | `value` (array)/`onChange`, autocomplete suggestions, configurable confirm keys, closable tags, `color`, `size`. |
90
+ | Component | Key props |
91
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
92
+ | `Autocomplete` | `data` (`string[]` or item objects), `value`, `onInput(value)` for typing, `onSelect(item)` for picks, `clearable` (the clear button hides while `loading`, or a loading `Control` around it, puts a spinner at the same edge), `openOnFocus`, `loading`. |
93
+ | `Combobox` | Either way: `multiple`, `keepFirst`, `inline`, `size`, `position` (`'auto'` keeps the list in the viewport), `appendToBody` (in a `Modal`, point a `ConfigProvider`'s `portalContainer` inside the modal), `open`/`defaultOpen`/`onOpenChange`, `onSelect(value)` and the Field props. Given `data` (strings or `{ value, label?, disabled?, group? }`): `value`/`defaultValue`/`onChange` (`string \| null`, or `string[]` with `multiple`, shown as tags), `placeholder`, `name`, `empty`, `itemTemplate`, and `disabled`, `required`, `form`, `id`, `aria-label`/`aria-labelledby` for the field; leaving the field empty clears one value. From parts: `.Input` (an `Input`, with `size` and the Field props on the root), `.Menu`, `.List`, `.Item` (`value`, `checked`, `disabled`), `.Group` (`label`), `.Empty`; you filter, keep the value and set the field's text in `onSelect`. |
94
+ | `Slider` | `min`, `max`, `step`, single or dual thumb (`value` number or `[number, number]`), `tooltip` (`'auto' \| 'always' \| 'hidden'`), vertical orientation, `color`, `size`. |
95
+ | `Numberinput` | `value`/`onChange`, `min`, `max`, `step`, increment/decrement buttons, stepper variant, `color`, `size`, `isLoading` (set it here, not on a `Control` around it, whose spinner covers the right-hand button). |
96
+ | `PinInput` | `length` (6), `value`/`defaultValue`/`onChange` (the code as a string), `onComplete(code)` once a change fills the last box, `type` (`'number'` or `'alphanumeric'`), `isMasked`, `name`, `color`, `size`, `isRounded`, `disabled` and the Field props. One input holds the code (`autoComplete="one-time-code"`); a paste replaces it. |
97
+ | `Rate` | `value`/`onChange`, `max`, `precision` (1 / 0.5 / 0.25), `customIcon` or `iconName`, `showScore`/`showText`/`texts`, `disabled`. |
98
+ | `Taginput` | `value` (array)/`onChange`, autocomplete suggestions, configurable confirm keys, closable tags, `color`, `size`. |
94
99
 
95
100
  ## Date / time inputs
96
101
 
@@ -131,7 +131,7 @@ Notes:
131
131
  // Search box: input + button attached.
132
132
  <Field hasAddons>
133
133
  <Control isExpanded>
134
- <InputBase placeholder="Search" />
134
+ <InputBase placeholder="Search" aria-label="Search" />
135
135
  </Control>
136
136
  <Control>
137
137
  <Button color="primary">Search</Button>
@@ -141,14 +141,18 @@ Notes:
141
141
  // Two controls on one row.
142
142
  <Field grouped>
143
143
  <Control>
144
- <InputBase placeholder="First" />
144
+ <InputBase placeholder="First" aria-label="First name" />
145
145
  </Control>
146
146
  <Control>
147
- <InputBase placeholder="Last" />
147
+ <InputBase placeholder="Last" aria-label="Last name" />
148
148
  </Control>
149
149
  </Field>
150
150
  ```
151
151
 
152
+ A `grouped` or `hasAddons` Field names none of the controls it holds, and a placeholder
153
+ disappears once the user types, so each input above carries an `aria-label`. For a visible
154
+ label instead, see the last item below.
155
+
152
156
  Recipes built on addons, each written out in full in the docs:
153
157
 
154
158
  - **Password reveal** ([docs](https://bestax.io/docs/api/form/input#password-reveal)): the
@@ -210,6 +214,60 @@ const [city, setCity] = useState('');
210
214
  openOnFocus
211
215
  />;
212
216
 
217
+ // Combobox: a value from a list. Given data, it filters by label, keeps the
218
+ // value and shows its label in the field.
219
+ const [country, setCountry] = useState<string | null>(null);
220
+ <Combobox
221
+ label="Country"
222
+ data={['Canada', 'France', 'Japan']}
223
+ value={country}
224
+ onChange={setCountry}
225
+ empty="No countries match"
226
+ />;
227
+
228
+ // Several values, shown as tags under the field; groups come from `group`.
229
+ const [labels, setLabels] = useState<string[]>([]);
230
+ <Combobox
231
+ label="Labels"
232
+ multiple
233
+ data={[
234
+ { value: 'bug', label: 'Bug', group: 'Type' },
235
+ { value: 'docs', label: 'Docs', group: 'Area' },
236
+ ]}
237
+ value={labels}
238
+ onChange={setLabels}
239
+ />;
240
+
241
+ // From parts, when data doesn't fit (a server search, a "Create" item): you
242
+ // filter, keep the value and set the text, and a pick calls onSelect.
243
+ const countries = ['Canada', 'France', 'Japan'];
244
+ const [choice, setChoice] = useState<string | null>(null);
245
+ const [query, setQuery] = useState('');
246
+ const term = query === choice ? '' : query.trim().toLowerCase();
247
+ const matches = countries.filter(c => c.toLowerCase().includes(term));
248
+ <Combobox
249
+ label="Country"
250
+ onSelect={picked => {
251
+ setChoice(picked);
252
+ setQuery(picked);
253
+ }}
254
+ >
255
+ <Combobox.Input value={query} onChange={e => setQuery(e.target.value)} />
256
+ <Combobox.Menu>
257
+ {matches.length > 0 ? (
258
+ <Combobox.List>
259
+ {matches.map(c => (
260
+ <Combobox.Item key={c} value={c} checked={c === choice}>
261
+ {c}
262
+ </Combobox.Item>
263
+ ))}
264
+ </Combobox.List>
265
+ ) : (
266
+ <Combobox.Empty>No countries match</Combobox.Empty>
267
+ )}
268
+ </Combobox.Menu>
269
+ </Combobox>;
270
+
213
271
  // Slider (single and dual thumb)
214
272
  const [volume, setVolume] = useState(50);
215
273
  <Slider value={volume} onChange={setVolume} min={0} max={100} tooltip="auto" />;
@@ -229,6 +287,20 @@ const [range, setRange] = useState<[number, number]>([20, 80]);
229
287
  const [qty, setQty] = useState(1);
230
288
  <Numberinput value={qty} onChange={setQty} min={1} max={10} step={1} />;
231
289
 
290
+ // PinInput: a one-time code or PIN, a box per character, all in one input.
291
+ // onComplete fires once each time a change fills the last box.
292
+ const [code, setCode] = useState('');
293
+ const verify = (otp: string) => {
294
+ // Send it to your server, then show the result with color and message.
295
+ };
296
+ <PinInput
297
+ label="Enter the 6-digit code"
298
+ name="otp"
299
+ value={code}
300
+ onChange={setCode}
301
+ onComplete={verify}
302
+ />;
303
+
232
304
  // Rate
233
305
  const [stars, setStars] = useState(3);
234
306
  <Rate value={stars} onChange={setStars} max={5} />;
@@ -97,6 +97,31 @@ Centered; a collection of items → Card grid. For mixed requests, pick the domi
97
97
  "Clear search" put focus back in the field. While data loads show a `Skeleton` or a `Loader`,
98
98
  and a failed load is a `Message` or a `Notification`, never an EmptyState. The archetypes
99
99
  reference has the full pattern under Card grid.
100
+ - **An order's history, a ticket's activity, a changelog or an activity feed is `Timeline`**,
101
+ not a hand-built list with a border down the side: `<Timeline titleAs="h3">` with a
102
+ `<Timeline.Item title="Shipped" time="14 October 2026" dateTime="2026-10-14">` per event, any
103
+ detail as its children, and the whole date in `time`. `titleAs` is the level under the
104
+ section's heading, or `"p"` in a busy feed. For something that moves along on its own, like a
105
+ parcel or a deployment, `active={i}` marks progress and `pending="Out for delivery"` ends it on
106
+ what's under way, oldest first. A journey the user moves through, like a wizard or a checkout,
107
+ stays `Steps`.
108
+ - **A file browser, a checkbox tree (permissions, filters) or an outline you browse is
109
+ `TreeView`**, with `<TreeView.Item value="…" label="…" icon="folder">` items nested inside each
110
+ other: `<TreeView aria-label="Files" selectionMode="single">` with `selected`/`onSelectedChange`
111
+ picks one, and `selectionMode="checkbox"` with `checked`/`onCheckedChange` draws boxes with
112
+ mixed parents, where a parent's value checks its whole branch. Map the data with a recursive
113
+ function, and render an item's children whether it's open or not. A folder that loads when it
114
+ opens takes `hasChildren`, `isLoading` while its children are on their way, and
115
+ `loadError="Couldn't load src"` with `onRetry` when the load fails, setting `isLoading` again
116
+ while the retry runs: the tree draws the Loading… and Retry rows and announces them, so don't
117
+ hand-write a placeholder item. Its items aren't
118
+ links, so the app's sidebar and any list of pages stay on `Menu` with `Menu.Item href`.
119
+ - **A sidebar whose groups of links open and close is `Menu.Section`**, one per group, directly
120
+ in `Menu` (never inside a `Menu.Item` or `Menu.List`):
121
+ `<Menu.Section label="Components" defaultOpen>` around that group's `Menu.List`, with
122
+ `defaultOpen` on the group holding the current page (or `open`/`onOpenChange` to hold the state).
123
+ Its heading is a real button with `aria-expanded`, and its links stay links, each a tab stop.
124
+ Not `TreeView`, and not a `Collapse` inside a hand-written `<li>`.
100
125
  - Link lists (footer nav, sidebars): a bare `UnorderedList` of `ListItem`s is already
101
126
  marker-less and flush — Bulma's reset unstyles `ul` — so no prop or CSS is needed;
102
127
  bullets appear only inside `Content`.
@@ -152,9 +177,10 @@ Centered; a collection of items → Card grid. For mixed requests, pick the domi
152
177
  (`Hero color="primary"`, a dark banner), use **filled** buttons — `color="light"` or
153
178
  `color="primary" isInverted` — never a thin `isOutlined` secondary: a light outline + light
154
179
  label on a dark surface is low-contrast and gets worse under OS dark mode. And when the page's
155
- design is single-mode (a fixed light or dark look), pin it at the root —
156
- `<Theme isRoot colorMode="light">` — so a visitor's OS dark mode can't flip Bulma's text
157
- colors out from under the fixed palette (details: the `bestax-theming` skill's contrast rules).
180
+ design is single-mode (a fixed light or dark look), pin it on the `<html>` in `index.html`,
181
+ `<html lang="en" data-theme="light">`, so a visitor's OS dark mode can't flip Bulma's text
182
+ colors out from under the fixed palette, from the first paint on (details: the
183
+ `bestax-theming` skill's contrast rules).
158
184
 
159
185
  ## Three components core Bulma will talk you out of
160
186
 
@@ -199,9 +225,11 @@ background comes from the semantic `color` prop, not `bgColor`.
199
225
  | `display: 'none'` | `visibility="hidden"`, or responsive `display*` props (`displayMobile`, `displayTablet`, …) |
200
226
  | `gap: '1rem'` (flex or grid) | `gap="2"`, or one axis: `columnGap`, `rowGap`; gap scale: `1`=0.5rem, `2`=1rem, `4`=2rem, half steps (`"1.5"`) |
201
227
 
202
- No helper matches (e.g. `maxWidth`, a one-off gradient)? Add a named class to the project
203
- stylesheet (`src/App.css` in a scaffolded app) and pass it via `className` — still never
204
- inline `style`.
228
+ No helper matches (e.g. `maxWidth`, a one-off gradient)? Add a named class to the project's
229
+ global stylesheet and pass it via `className` — still never inline `style`. In a create-bestax
230
+ app that is `src/index.css`, which its entry file imports after bestax's stylesheet, so the
231
+ class wins over a Bulma rule of the same weight. The starter page's `src/App.css` loads after
232
+ bestax's too, but only while the starter's `App` imports it.
205
233
 
206
234
  ## References
207
235
 
@@ -164,8 +164,8 @@ style, so bands cost zero custom CSS. The same values work on `Hero` (and its
164
164
  **Hero CTAs:** on a colored hero use **filled** buttons only — `color="light"` for the primary
165
165
  CTA and `color="primary" isInverted` (solid white, primary text) for a secondary. A thin
166
166
  `isOutlined` button on a fixed-color surface is low-contrast and degrades further under OS dark
167
- mode. Single-mode page designs should also pin the scheme at the root
168
- (`<Theme isRoot colorMode="light">`) — see the `bestax-theming` skill's contrast rules.
167
+ mode. Single-mode page designs should also pin the scheme on the `<html>` in `index.html`
168
+ (`<html lang="en" data-theme="light">`); see the `bestax-theming` skill's contrast rules.
169
169
 
170
170
  ---
171
171
 
@@ -39,12 +39,16 @@ Every component also accepts the shared Bulma helper props (`m`/`p` spacing, `te
39
39
 
40
40
  | Prop | Type | Notes |
41
41
  | --------------------------------- | --------------------------------------- | ----------------------------------------- |
42
+ | `as` | `'div'` (default) or a landmark tag | Renders that tag in place of the `div`. |
42
43
  | `fluid` | `boolean` | Full width with a small gutter. |
43
44
  | `widescreen` / `fullhd` | `boolean` | Only constrain at that breakpoint and up. |
44
45
  | `breakpoint` | `'tablet' \| 'desktop' \| 'widescreen'` | Max-width breakpoint. |
45
46
  | `isMax` | `boolean` | Use the `is-max-*` width. |
46
47
  | `color` / `textColor` / `bgColor` | Bulma color | — |
47
48
 
49
+ `as` takes `'main' | 'section' | 'article' | 'aside' | 'header' | 'footer' | 'nav'`, so
50
+ `<Container as="main">` renders `<main class="container">` with no wrapper.
51
+
48
52
  ## Section
49
53
 
50
54
  `<Section>` adds vertical page rhythm (padding).
@@ -63,6 +67,7 @@ Every component also accepts the shared Bulma helper props (`m`/`p` spacing, `te
63
67
  | `color` | `primary \| link \| info \| success \| warning \| danger \| black \| white \| light \| dark` (CSS-backed). Also accepted but deprecated, no CSS, dev-warn: every other `validColors` member, plus `inherit` and `current` |
64
68
  | `size` | `'small' \| 'medium' \| 'large' \| 'fullheight' \| 'fullheight-with-navbar'` |
65
69
  | `fullheightWithNavbar` | `boolean` |
70
+ | `as` | `'section'` (default) \| `'div'` \| `'main'` \| `'header'` \| `'footer'` \| `'article'` \| `'aside'` |
66
71
 
67
72
  ```tsx
68
73
  <Hero color="primary" size="medium">
@@ -70,6 +75,11 @@ Every component also accepts the shared Bulma helper props (`m`/`p` spacing, `te
70
75
  </Hero>
71
76
  ```
72
77
 
78
+ `Hero.Head` takes `as="header"` or `as="nav"` and `Hero.Foot` `as="footer"` or `as="nav"` (both
79
+ `div` by default), so a page that is all hero is `<Hero as="main">` with no wrapper. A `header` or
80
+ `footer` is the page's banner or footer landmark only outside a `section`, `article`, `aside`,
81
+ `nav` or `main`, so for that render the hero itself `as="div"`.
82
+
73
83
  ## Footer
74
84
 
75
85
  `<Footer>` is the page footer.
@@ -230,6 +240,8 @@ flexDirection="column"` + `flexGrow="1"` pattern applies to `Cell` + `Card`.
230
240
  | --------------- | ---------------------------------------------------------------------------------------------------------------- |
231
241
  | `fixed` | `'top' \| 'bottom'` |
232
242
  | `transparent` | `boolean` |
243
+ | `isSpaced` | `boolean` (Bulma's `is-spaced`, from desktop up) |
244
+ | `hasShadow` | `boolean` (default `false`, unlike `Card`'s; Bulma's `has-shadow`) |
233
245
  | `color` | `'primary' \| 'link' \| 'info' \| 'success' \| 'warning' \| 'danger' \| 'black' \| 'dark' \| 'light' \| 'white'` |
234
246
  | `Navbar.Item` | `as?`, `active?`, `href` |
235
247
  | `Navbar.Burger` | `active?`, `onClick` |
@@ -239,7 +251,12 @@ The burger/menu is controlled state — toggle `Navbar.Burger active`/`onClick`
239
251
  flag to `Navbar.Menu active`. **A `fixed="top"` navbar requires `has-navbar-fixed-top` on `<html>`**
240
252
  (Bulma offsets the page from it); the library adds no helper. Set it statically —
241
253
  `<html class="has-navbar-fixed-top">` in `index.html` — reserving a `classList.add` effect for
242
- a navbar that mounts conditionally.
254
+ a navbar that mounts conditionally. **With `isSpaced`, add `has-spaced-navbar-fixed-top` too**
255
+ (`<html class="has-navbar-fixed-top has-spaced-navbar-fixed-top">`): from desktop up the spaced
256
+ navbar is taller than the plain class pads the page for, and there the spaced class pads it for
257
+ the taller navbar, so with the plain class alone the top of the page sits under the navbar. A
258
+ `fixed="bottom"` navbar takes `has-navbar-fixed-bottom`, and `has-spaced-navbar-fixed-bottom`
259
+ beside it with `isSpaced`.
243
260
 
244
261
  **Routing:** in a routed app, don't use `href="#"` — render items as the router's link
245
262
  component. `Menu.Item as={Link} to="/x"` and `Navbar.Item as={Link} to="/x"` both compile
@@ -250,11 +267,13 @@ https://bestax.io/docs/guides/features/routing.
250
267
 
251
268
  ## Menu
252
269
 
253
- `<Menu>` is a vertical sidebar menu. Subcomponents: `Menu.Label`, `Menu.List`, `Menu.Item`.
270
+ `<Menu>` is a vertical sidebar menu. Subcomponents: `Menu.Label`, `Menu.List`, `Menu.Item`,
271
+ `Menu.Section`.
254
272
 
255
- | Prop | Type |
256
- | ----------- | ------------------------------------------------------------------------------------------------- |
257
- | `Menu.Item` | `active?`, `href?`, `as?` (custom link component); nest a `Menu.List` inside an item for submenus |
273
+ | Prop | Type |
274
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
275
+ | `Menu.Item` | `active?` (the current page: `is-active` plus `aria-current="page"`, so don't add your own), `href?`, `as?` (custom link component); nest a `Menu.List` inside an item for submenus |
276
+ | `Menu.Section` | `label` (the heading, a `ReactNode`), `defaultOpen?`, or `open?` with `onOpenChange?(open)` to hold the state yourself, as `Collapse` does; its child is the group's `Menu.List`, and a closed section's links are out of reach |
258
277
 
259
278
  ```tsx
260
279
  <Menu>
@@ -268,6 +287,34 @@ https://bestax.io/docs/guides/features/routing.
268
287
  </Menu>
269
288
  ```
270
289
 
290
+ A sidebar whose groups of links open and close (a docs sidebar, an admin app's sections) puts each
291
+ group's `Menu.List` in a `Menu.Section`. Its heading is a real button drawn like `Menu.Label`, with
292
+ `aria-expanded` and a chevron, and its links stay ordinary links, each a tab stop. Open the
293
+ section that holds the current page. A section goes directly in `Menu`, never inside a `Menu.Item`
294
+ (it would render inside the item's link) or a `Menu.List` (it isn't a list item). Don't build this
295
+ from `TreeView` (its items aren't links) or from a `Collapse` in a hand-written `<li>` (its trigger
296
+ is a `div` with `role="button"`).
297
+
298
+ ```tsx
299
+ <nav aria-label="Documentation">
300
+ <Menu>
301
+ <Menu.Section label="Getting started">
302
+ <Menu.List>
303
+ <Menu.Item href="/docs/install">Installation</Menu.Item>
304
+ </Menu.List>
305
+ </Menu.Section>
306
+ <Menu.Section label="Components" defaultOpen>
307
+ <Menu.List>
308
+ <Menu.Item href="/docs/button" active>
309
+ Button
310
+ </Menu.Item>
311
+ <Menu.Item href="/docs/card">Card</Menu.Item>
312
+ </Menu.List>
313
+ </Menu.Section>
314
+ </Menu>
315
+ </nav>
316
+ ```
317
+
271
318
  ## Card
272
319
 
273
320
  `<Card>` for catalog/grid items. Use the props form (`header`, `image`, `footer`) or the compound