Skip to content
Back to skills

Bestax Layout Scaffold

ASecurity

Scaffold a complete, responsive page layout with @allxsmith/bestax-bulma — app shells/dashboards, marketing/landing pages, centered auth/settings pages, and card-grid catalogs. Use when building a full page or overall app layout (not a single component).

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 10, 2026
ai-agentsgoshellnodeawsapi

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 8 files and shows the line behind each finding

Scanned November 10, 2026

npx -y skills add allxsmith/bestax-skills --skill bestax-layout-scaffold --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Bestax Layout Scaffold?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Bestax Layout Scaffold
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/allxsmith-bestax-layout-scaffold-bestax-skills/badge)](https://www.skillsdirectory.com/skills/allxsmith-bestax-layout-scaffold-bestax-skills)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: bestax-layout-scaffold
description: Scaffold a complete, responsive page layout with @allxsmith/bestax-bulma — app shells/dashboards, marketing/landing pages, centered auth/settings pages, and card-grid catalogs. Use when building a full page or overall app layout (not a single component).
license: MIT
---

# Scaffolding a page layout with @allxsmith/bestax-bulma

Turn a high-level request ("admin dashboard", "landing page", "login screen", "product catalog")
into a complete responsive page built from bestax-bulma layout components.

## Behavioral rule

Select an archetype from the request and build it in one shot. Do **not** ask layout questions
("how many columns?", "where should the nav go?", "what width?") — infer the structure from the
request and proceed. The archetype determines the structure; fill it with the requested content.

Ask **at most one** clarifying question, and only for a high-level fork the request genuinely does
not imply: whether the page is **public-facing** (marketing) or an **internal tool** (authenticated
app). When the request already signals this ("dashboard", "admin", "landing", "login", "pricing"),
skip the question and default.

## Select an archetype

| Request signals                                                        | Archetype     |
| ---------------------------------------------------------------------- | ------------- |
| dashboard, admin, console, internal tool, authenticated app, "sidebar" | **App shell** |
| landing, marketing, homepage, product/pricing page, public site        | **Landing**   |
| login, sign up, auth, settings, checkout, a single focused form        | **Centered**  |
| catalog, gallery, products, listing, "grid of cards", search results   | **Card grid** |

Default when ambiguous: internal tool → App shell; public-facing → Landing; one focused task →
Centered; a collection of items → Card grid. For mixed requests, pick the dominant intent (e.g.
"admin dashboard with a product list" → App shell whose main column holds a Card grid).

## Approach

- Compose pages from the shipped layout components — `Container`, `Section`, `Hero`, `Footer`,
  `Level`, `Columns`/`Column`, `Grid`/`Cell`, `Navbar`, `Menu`, `Card`. There is **no `Tile`
  component**. For **uniform grids** (card grids, galleries — same-shaped items) prefer
  `Grid`/`Cell`: CSS Grid gives equal-height cells for free (per row — each row's cells match
  its tallest, same row-level behavior as the flex recipe). Use `Columns`/`Column` for
  proportional or per-breakpoint column layouts — and when cards there must be equal height,
  apply the flex recipe (`Column display="flex" flexDirection="column"` + `Card flexGrow="1"`;
  `height: 100%` on the card doesn't help — the column's height is auto).
- Rely on Bulma's responsive defaults: `Columns` sit side by side on tablet and up and stack on
  mobile. Add responsive `size*` props only to tune the breakpoints.
- Interactive extras don't share a state API — never transfer one by analogy:
  `Collapse trigger={node} open/defaultOpen onOpenChange` (`onOpenChange(open)` reports every
  trigger; `onOpen/onClose` fire only when the Collapse holds its own state), an accordion is
  `Collapses value/defaultValue/onChange` around plain `Collapse` children indexed from 0 by
  position (`number | null`, or `number[]` with `multiple`; hide an item with
  `visibility="hidden"`, never `{cond && …}`, or the open state shifts to its neighbour) rather
  than hand-rolled open-index state,
  `Tabs value={i}/onChange`
  (each `Tabs.Tab`/`Tabs.Content.Item` requires `index={i}`, and `Tabs.Content` must be a
  **child of `<Tabs>`** — the active-tab context lives on it; a sibling panel never
  switches. The arrow keys, Home/End, Enter/Space, the single tab stop and the
  `aria-controls`/`aria-labelledby` links come built in, so add no `onKeyDown` or ARIA
  of your own; `onChange` fires when a tab is activated, never on
  arrow-key focus), `Dropdown active/onActiveChange`,
  `ToggleButton pressed/defaultPressed onPressedChange` for a button that stays on or off (not
  `Button isActive`, which sets no `aria-pressed`; the label stays the same in both states; a
  set goes in `Buttons hasAddons role="group"` with an `aria-label`, where it's also
  `Buttons.ToggleButton`, and a row where only one can be on is `SegmentedControl`),
  `SegmentedControl aria-label="…" value/defaultValue onChange` for a row of buttons where
  only one can be on, such as a list or grid view (one `SegmentedControl.Segment value="…"` per
  option; a radio group with the single tab stop and the arrow keys built in, so add no
  `onKeyDown` of your own; an icon-only segment needs its own `aria-label`; the arrows check as
  they move, so `onChange(value)` fires on each, and it can't be cleared; it carries no form
  value, so a choice a form submits is `Radios`),
  `Popover trigger={<Button>…</Button>} open/defaultOpen onOpenChange` (`trigger` is one
  element, not a node: Popover clones it to add the click and ARIA, so never wrap it in a
  clickable of your own; build the panel from `Popover.Header`/`Body`/`Footer`/`Close`, and a
  panel without a `Popover.Header` needs `ariaLabel`),
  `HoverCard trigger={<Link href="…">…</Link>}` for a preview on hover or keyboard focus
  (`trigger` is one link or button, cloned the same way; the card's content goes straight in as
  children with no sub-components; a trigger inside a `Paragraph` needs `appendToBody`; hover
  and focus only unless `openOnTap` is set, which suits only a trigger with no tap action of its
  own, such as a `Button` with no `onClick`; on touch a link trigger just follows the link, so
  what the card shows must be on that page too, and a form is a `Popover`),
  `ContextMenu aria-label="…" trigger={<Table.Tr>…</Table.Tr>}` for actions on a right-click
  or a long press (`trigger` is one element, a row or a card, cloned rather than wrapped; the
  items are `ContextMenu.Item`/`ContextMenu.Divider`, which are `Dropdown`'s; no `open` prop,
  `onOpenChange` reports it; always put the same items in a visible `Dropdown` on the row too,
  since a context menu is only a shortcut),
  `Steps value={i}/onStepClick items={[{label, icon?}]}` for a journey the user moves through,
  such as a wizard or a checkout (child form is `Steps.Step`, not `Steps.Item`; a record of
  what happened is `Timeline`). `Reveal cascade` staggers only its **direct children** — to
  stagger a grid, put `<Reveal delay={i * 80}>` inside each `Cell`, not around the container.
- **An empty list, table or search with no results is `EmptyState`**, not a hand-built `Box`
  of `Icon`, `Title` and `Buttons isCentered`:
  `<EmptyState icon="folder-open" title="No projects yet" titleAs="h3" description="…">` with
  its one `<Button>` as a child (two or more go in `EmptyState.Actions`). Its `title` is always a
  heading, so set `titleAs` to the level under the list's own heading. It has no live region:
  after a search or a filter, keep a `Paragraph` with `role="status"`, `aria-atomic="true"` and
  `visibility="sr-only"` beside the search field the whole time, saying the count, and have
  "Clear search" put focus back in the field. While data loads show a `Skeleton` or a `Loader`,
  and a failed load is a `Message` or a `Notification`, never an EmptyState. The archetypes
  reference has the full pattern under Card grid.
- **A table people sort, search, page through or pick rows from is `DataTable`**, not a `Table`
  with sort state, `aria-sort`, checkboxes and a `Pagination` wired by hand:
  `<DataTable caption="Users" columns={[{ id: 'name', header: 'Name', sortable: true, isRowHeader: true }, …]} data={rows} selectionMode="multiple" pageSize={10} hasSearch />`.
  A column is `{ id, header }`, reading `row[id]`, plus `accessor` (the raw value, which sorts),
  `format` (its text, which the cell shows and the search matches) and `cell` (rich content).
  It needs a `caption`, `aria-label` or `aria-labelledby`, and it shows its own `EmptyState` and
  says the match count itself. `toolbar={table => …}` holds buttons that act on
  `table.state.selected` or call `table.setFilter`; `useDataTable({ columns, data, … })` returns
  the same table for `<DataTable table={table} />` or for markup of your own through its
  `get…Props` getters; `manual` with `rowCount` hands sorting, filtering and paging to a server.
  `table.toCSV()` writes what's shown as CSV, escaping formulas, for a download button; there's
  no Excel export. A table with nothing to sort, search or pick stays `Table`.
- **An order's history, a ticket's activity, a changelog or an activity feed is `Timeline`**,
  not a hand-built list with a border down the side: `<Timeline titleAs="h3">` with a
  `<Timeline.Item title="Shipped" time="14 October 2026" dateTime="2026-10-14">` per event, any
  detail as its children, and the whole date in `time`. `titleAs` is the level under the
  section's heading, or `"p"` in a busy feed. For something that moves along on its own, like a
  parcel or a deployment, `active={i}` marks progress and `pending="Out for delivery"` ends it on
  what's under way, oldest first. A journey the user moves through, like a wizard or a checkout,
  stays `Steps`.
- **Facts about one thing, each a label and its value (an order summary, a profile, a file's
  details), are `DescriptionList`**, not `Columns` of label and value or a two-column `Table`:
  `<DescriptionList aria-labelledby="order-heading">` under a `Title id="order-heading"`, with a
  `<DescriptionList.Item label="Order" value="#1234" />` per fact. It stacks on a phone and puts
  the labels beside their values from tablet up, so add no breakpoint code; `orientation`
  pins it. Several values, or a label with an info tip, go in as `DescriptionList.Label` and
  `DescriptionList.Value` children. Several records with the same fields stay a `Table`, and so
  do facts a screen reader must announce with their labels, as a `Table` with `scope="row"`
  headers.
- **A number shown to the reader (a price, a total, a count, a percentage, a dashboard figure) is
  `NumberFormatter`**, not `toLocaleString()` or an `Intl.NumberFormat` built by hand:
  `<NumberFormatter value={8456.4} format={{ style: 'currency', currency: 'USD' }} />`, with any
  `Intl.NumberFormat` options in `format` (`style: 'percent'` takes a fraction, `0.125` for
  12.5%). It writes in its own `locale`, else the `LocaleProvider`'s, else the runtime's, so a
  server-rendered app sets a `locale` on a `LocaleProvider` at the root. Its text is the number
  alone, so words go around it: `Total: <NumberFormatter … />`. Text that can't hold an element,
  an `aria-label` or a `placeholder`, gets the same from `const format = useNumberFormat();` and
  `format(1500, { style: 'currency', currency: 'EUR' })`.
- **A dashboard's row of key figures (revenue, new customers, churn) is `Stats` of `Stat`s**, not
  `Level` items, `Columns` of `Title`s or a `Card` per figure. Put
  `<Stats aria-labelledby="kpis" minCol={8} gap={4}>` under a `Title id="kpis"`, with a `Stat` per
  figure: `label`, a number `value` with `format` (`Intl.NumberFormat` options, as
  `NumberFormatter` takes), `change` and `description`, as in
  `<Stat label="Revenue" value={8456.4} change={0.12} description="since last month" />`.
  `change` is a fraction written with its sign ("+12%"), green up and red down, and
  `changeColor="danger"` marks a rise that's bad news, such as churn. The row wraps on its own, so
  add no breakpoint code, and it holds only `Stat`s (a `LocaleProvider` goes around it, and on a
  page rendered on the server it's what keeps the server and the browser writing the numbers
  alike, unless each `Stat` gets a `locale`). `loading`
  shows a `Skeleton` in place of a figure that isn't there yet. One figure on its own is a lone
  `Stat`. For a card look, a `Box` goes around a lone `Stat` or the whole `Stats` row, never around
  one `Stat` inside a row, which would break the row's list.
- **A file browser, a checkbox tree (permissions, filters) or an outline you browse is
  `TreeView`**, with `<TreeView.Item value="…" label="…" icon="folder">` items nested inside each
  other: `<TreeView aria-label="Files" selectionMode="single">` with `selected`/`onSelectedChange`
  picks one, and `selectionMode="checkbox"` with `checked`/`onCheckedChange` draws boxes with
  mixed parents, where a parent's value checks its whole branch. For categories, where checking a
  parent means only the parent, `selectionMode="checkbox-independent"` draws a box per item that
  checks that item alone, with no mixed parents. Map the data with a recursive
  function, and render an item's children whether it's open or not. A folder that loads when it
  opens takes `hasChildren`, `isLoading` while its children are on their way, and
  `loadError="Couldn't load src"` with `onRetry` when the load fails, setting `isLoading` again
  while the retry runs: the tree draws the Loading… and Retry rows and announces them, so don't
  hand-write a placeholder item. Its items aren't
  links, so the app's sidebar and any list of pages stay on `Menu` with `Menu.Item href`.
- **A sidebar whose groups of links open and close is `Menu.Section`**, one per group, directly
  in `Menu` (never inside a `Menu.Item` or `Menu.List`):
  `<Menu.Section label="Components" defaultOpen>` around that group's `Menu.List`, with
  `defaultOpen` on the group holding the current page (or `open`/`onOpenChange` to hold the state).
  Its heading is a real button with `aria-expanded`, and its links stay links, each a tab stop.
  Not `TreeView`, and not a `Collapse` inside a hand-written `<li>`.
- **A long page's "On this page" list of its sections is `TableOfContents`**, not a `Menu` with
  a scroll listener of your own: give each section's heading an `id` and pass the sections in page
  order, `<TableOfContents isSticky items={[{ id: 'install', label: 'Install' }, { id: 'usage', label: 'Usage', children: […] }]} />`,
  in a side `Column`. Its links are plain `#id` links that the browser follows itself, and it marks the
  section being read with `aria-current="location"`. Offset a sticky header with
  `scroll-padding-top` on `html` in the page's CSS (which the highlight reads too), not with
  `scroll-margin-top` on the headings, and put any smooth scrolling in that CSS under
  `prefers-reduced-motion: no-preference`. To mark the section in view in the app's own nav
  `Menu`, `useScrollSpy({ ids })` returns its id.
- **An app's frame, a header across the top with a navigation column down the side, is
  `AppFrame`**, not a `fixed` `Navbar` over `Columns`: `<AppFrame>` with `AppFrame.Header` (a
  `Navbar` with `<AppFrame.Burger />` in its `Navbar.Brand`), `AppFrame.Nav` (the `Menu`) and
  `AppFrame.Main` (the page), plus `AppFrame.Aside` and `AppFrame.Footer` when there are any. The
  header and the column stick on their own, so no `fixed` and no `has-navbar-fixed-top`; below
  desktop the column is a drawer the burger opens, and from desktop up the burger collapses it.
  It renders the page's `<main>`, so it goes at the top of the app, never inside a `<main>`.
- **Panes the user resizes are `Splitter`** (a file tree beside an editor, a list beside its
  detail, a query above its results), not hand-written drag code or fixed `Columns`:
  `<Splitter height={480}>` with a `<Splitter.Pane label="Files" defaultSize={25} minSize="12rem">`
  per pane. The handles go in on their own, each named by its pane's `label`, so give every pane
  one. Sizes are percentages, limits also take `px` or `rem`, `orientation="vertical"` stacks
  them (and needs a `height`), and `collapsible` with `collapsed`/`onCollapsedChange` lets a
  button hide a pane. Put scrolling content in a `ScrollArea` inside the pane.
- **Content taller or wider than its space is `ScrollArea`** (release notes in a Card, a sidebar
  longer than the screen, a feed that grows then scrolls, a wide table on a phone), not a `Box`
  with `overflowY="auto"` and a height in a style: `<ScrollArea height={240} aria-label="…">`,
  or `maxHeight="20rem"` to grow with its content first. It needs `aria-label` or
  `aria-labelledby`, since while it overflows it's a region a keyboard user tabs to. The
  helper props and `className` go on its frame and the `ref` on the element that scrolls.
  `hasFade` fades the edges that have more, `orientation="horizontal"` suits a wide table, and
  `onEdgeReach={edge => …}` loads more at an edge.
- **Something that stays in a corner of the screen (a floating button, a chat or help launcher,
  a feedback tab, a consent notice) is `Affix`**, not `pos="fixed"` with offsets in a style:
  `<Affix position="bottom-left"><Button color="primary">Feedback</Button></Affix>`.
  `position` is one of the four corners, `'bottom-right'` by default, and stays physical in
  right-to-left text. The spacing helpers nudge one further in (`mb="6"` lifts it over a bottom
  bar), and `portal` moves it out of an ancestor with a `transform`. Two in one corner overlap,
  so put what shares a corner in one Affix. Tab reaches it where it is in the markup, whatever
  corner it shows in, so write it after the main content. For an element pinned once the page
  scrolls past it, use `pos="sticky"` with a `top` instead.
- **A command palette (⌘K or Ctrl+K) is `Command.Dialog`**, not a `Modal` with an `Input` and a
  hand-written `keydown` listener: `<Command.Dialog shortcut="mod+k" placeholder="…" actions={[…]} />`,
  each action `{ id, label, onSelect }` with `group`, `keywords`, `description`, `icon` and `hint`
  (a `Kbd.Group`) as it needs them. Typing filters the actions by label, keywords and description,
  Enter runs the top match, and running one closes the dialog. `shortcut` is off unless given, and
  it does nothing while the user types in a field. A button that opens it too uses `open` and
  `onOpenChange`, with `aria-keyshortcuts` on the button. The same list inline, in a page or a
  Popover, is `Command`. For server results, build it from parts: `Command.Input`, then
  `Command.List` of `Command.Item`s, or `Command.Empty`, filtered from `onQueryChange`.
- Link lists (footer nav, sidebars): a bare `UnorderedList` of `ListItem`s is already
  marker-less and flush — Bulma's reset unstyles `ul` — so no prop or CSS is needed;
  bullets appear only inside `Content`.
- `Navbar.Burger`/`Navbar.Menu` are **controlled** — wire the same `active` state to both:
  `active` on `Navbar.Menu` shows/hides the mobile menu, while `active` + `onClick` on
  `Navbar.Burger` make the burger toggle it and animate. Left unwired, clicking the burger
  does nothing (no error, silent failure). For a `fixed="top"` `Navbar`, add the
  `has-navbar-fixed-top` class to `<html>` so content is not hidden behind it — the library
  does not do this automatically, and an inline padding offset is not a substitute.
- **Style with helper props — no inline `style`, no raw Bulma `className`s.** Before writing
  `style={{ … }}` anywhere, translate each declaration with the mapping table below — the helper
  props cover the common cases. Bare markup has wrapper elements that take all
  helper props: `<Span textSize="7" textColor="grey">`, `Paragraph`, `Strong` — never a raw
  `<span className="is-size-7 has-text-grey">`. Table cells: `Th`/`Td` take `textAlign="right"`,
  `textWeight`, `textSize` directly (their `color` prop colors the cell; for muted cell text
  wrap content in `Span textColor="grey"`). Set the app-wide icon library once with
  `<ConfigProvider iconLibrary="…">` at the root rather than `library` on every `<Icon>`. An app
  in another language, or a right-to-left one, also gets `lang` and `dir` on `<html>` and a
  `<LocaleProvider locale="…">` at the root, so overlays and date pickers follow it.
- **Alternating section bands are a prop, not CSS: `bgColor="scheme-main-bis"` on every
  other `Section` (next band `scheme-main-ter`).** The scheme values render as a
  dark-mode-safe inline `background-color: var(--bulma-scheme-*)` — zero custom CSS — and
  still never `bgColor="light"`/`"white"`: those are fixed colors that stay light when dark
  mode flips the text.
- **Decorative CSS is budgeted: two compact rules, ≤10 lines per app — comments count:
  at most one short inline note, never a file-header comment block — every value derived
  from `--bulma-*`.** A marketing page gets at most one hero wash and one featured-card
  ring, applied via `className` — no resets (Bulma ships one;
  body/list margins are already zero) and no grid textures, masks, or multi-layer backdrops;
  the components carry the design:

  ```css
  .hero-wash {
    background-image: radial-gradient(
      60rem 30rem at 20% -10%,
      hsl(var(--bulma-primary-h) var(--bulma-primary-s) 50% / 0.2),
      transparent 60%
    );
  }
  .featured-ring {
    --bulma-shadow: 0 0 0 2px var(--bulma-primary);
  }
  ```

  The ring works by overriding the **upstream token**, not the component's own var: `.card`
  and `.box` re-declare `--bulma-card-shadow`/`--bulma-box-shadow` from `--bulma-shadow` on
  their own selector, so setting _those_ from an ancestor never wins (same for
  `--bulma-box-radius`; `--bulma-card-radius` is a literal with no ancestor route at all).
  A class-scoped CSS rule keeps the ring on just the one featured card without wrapping it in
  its own `Theme`; `<Theme bulmaVars={{ '--bulma-shadow': … }}>` also compiles and is the
  better fit when the override already applies to a whole scoped subtree. Either way the
  subtree stays theme- and dark-mode-aware.

- **CTAs on a colored hero must stay legible in both schemes.** On a fixed-color surface
  (`Hero color="primary"`, a dark banner), use **filled** buttons — `color="light"` or
  `color="primary" isInverted` — never a thin `isOutlined` secondary: a light outline + light
  label on a dark surface is low-contrast and gets worse under OS dark mode. And when the page's
  design is single-mode (a fixed light or dark look), pin it on the `<html>` in `index.html`,
  `<html lang="en" data-theme="light">`, so a visitor's OS dark mode can't flip Bulma's text
  colors out from under the fixed palette, from the first paint on (details: the
  `bestax-theming` skill's contrast rules).

## Three components core Bulma will talk you out of

Most of this library's additions get found on their own, because nothing in Bulma does the
job. These three do not: each has a Bulma near-miss close enough to stop the search. Across
44 cold-start builds, `Dialog` was used **zero** times and `LinkButton` in two thirds — and
every miss shipped the "not this" column instead.

| You need                                                    | Use                                                                                                                                                             | Not this                                                                                                             |
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| A brief confirmation after an action, self-dismissing       | `Toast` — mount `<ToastContainer position="top-right" />` once at the app root, then `toast.success('Saved')` (also `.danger`/`.warning`/`.info`) from anywhere | `Notification`/`Message`                                                                                             |
| A confirm or alert the user must answer before anything     | `Dialog` — mount `<DialogContainer />` at the root, then `if (await dialog.confirm({ title, message })) …`                                                      | `Modal` — an empty shell; the title, message, button row and confirm/cancel wiring are all yours to rebuild          |
| A control that reads as text or a link but _does_ something | `LinkButton` (`variant="text" \| "ghost" \| "underline"`, optional `color`)                                                                                     | `<a href="#">`/`<div onClick>` (no keyboard or screen-reader support) or `Button color="text"` (still button-shaped) |

Mounting a container without ever calling `toast.*`/`dialog.*` is not usage — the container is
the mount point, the imperative call is the thing that shows something. Both also work as
ordinary controlled components when you would rather hold the state yourself —
`<Toast message … duration onClose>`, `<Dialog isOpen title message type onConfirm onCancel>` —
but in an app with more than one call site the root container plus the imperative helper is
less wiring, not more.

## Inline style → helper prop mapping

Look up the declaration you were about to inline. The spacing, typography, and flex helpers
below are on every component; `textColor`/`bgColor` are on the content components you'll
target (`Box`, `Block`, `Title`, `Content`, `Hero`, `Card`, …) — the ones with a semantic
`color` variant (`Tag`, `Tabs`, `Panel`) take `color` instead; wrap content in a `Block` if
you need a text color there. `Notification` is the mixed case: it takes `textColor`, but its
background comes from the semantic `color` prop, not `bgColor`.

| Inline style you're about to write       | Helper props instead                                                                                                                                           |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `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)                                 |
| `textAlign: 'center'`                    | `textAlign="centered"` (also `left`, `right`, `justified`)                                                                                                     |
| `color: '#…'`                            | `textColor` with the nearest Bulma color: `primary`, `link`, `info`, `success`, `warning`, `danger`, `white`, `black`, `grey` (+ `grey-light`, `grey-dark`, …) |
| `backgroundColor: '#…'`                  | `bgColor` (same palette)                                                                                                                                       |
| `fontSize: …`                            | `textSize="1"`…`"7"` (`1` largest). On `Title`/`SubTitle`, `as` sets the level and `size` picks it too, so an `<h3>` at size 4 is `as="h3" textSize="4"`       |
| `fontWeight: …`                          | `textWeight`: `light`, `normal`, `medium`, `semibold`, `bold`                                                                                                  |
| `textTransform`, italics                 | `textTransform`: `uppercase`, `lowercase`, `capitalized`, `italic`                                                                                             |
| `display: 'flex'` + flex properties      | same-named props: `display="flex"`, `flexDirection`, `justifyContent`, `alignItems`, `flexWrap`                                                                |
| `height: '100%'` on a flex child         | `flexGrow="1"`                                                                                                                                                 |
| `display: 'none'`                        | `visibility="hidden"`, or responsive `display*` props (`displayMobile`, `displayTablet`, …)                                                                    |
| `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"`)                                                 |

No helper matches (e.g. `maxWidth`, a one-off gradient)? Add a named class to the project's
global stylesheet and pass it via `className` — still never inline `style`. In a create-bestax
app that is `src/index.css`, which its entry file imports after bestax's stylesheet, so the
class wins over a Bulma rule of the same weight. The starter page's `src/App.css` loads after
bestax's too, but only while the starter's `App` imports it.

## References

- `references/layout-components.md` — the layout component inventory: real prop names, types, and
  accepted values, plus subcomponent nesting.
- `references/archetypes.md` — the four archetypes: selection criteria, JSX skeleton, and responsive
  behavior.

## Examples

- `examples/app-shell.tsx`: an `AppFrame` with a `Navbar` header, a `Menu` column and the page
  (dashboard).
- `examples/landing.tsx` — fixed `Navbar` (controlled burger) + `Hero` + `Section`s + `Footer`.
- `examples/centered.tsx` — centered single column (auth/settings).
- `examples/card-grid.tsx` — multiline `Columns` of `Card`s (catalog).
- `examples/content-page.tsx` — hero + feature cards + CTA styled with helper props (no inline
  `style`), wrapped in `ConfigProvider`.

## Checklist

- [ ] Map the request to one archetype; do not ask layout questions.
- [ ] Wrap page content in `Container` (+ `Section` for vertical rhythm).
- [ ] Use `Grid`/`Cell` for uniform grids (equal heights per row, free); `Columns`/`Column`
      for proportional or per-breakpoint side-by-side layout — with the flex recipe when its
      cards must match height.
- [ ] An app shell is `AppFrame`, with `AppFrame.Burger` in `Navbar.Brand`: no `fixed`, no
      `has-navbar-fixed-top`, and never inside a `<main>`.
- [ ] Elsewhere, wire `active` state to **both** `Navbar.Burger` and `Navbar.Menu` (they are
      controlled), and for a fixed navbar add `has-navbar-fixed-top` to `<html>`.
- [ ] Do not use `Tile` — it is not shipped.
- [ ] Action feedback goes through `Toast`, a confirmation through `Dialog`, a text-styled
      action through `LinkButton` — not `Notification`, `Modal` or a bare `<a>`.
- [ ] An empty list or a search with no results renders `EmptyState`, and a search or a filter
      keeps a `role="status"` region beside its field that says the count.
- [ ] Style with helper props, not inline `style` — translate via the mapping table; values
      with no helper get a named class in the stylesheet, never `style={{}}`. No raw Bulma
      `className`s either (`Span`/`Paragraph` wrap bare text; `Th`/`Td` take `textAlign`/`textWeight`).
- [ ] Alternating bands are `bgColor="scheme-main-bis"` (next band `scheme-main-ter`) on
      every other `Section` — never band CSS, never `bgColor="light"`/`"white"`.
- [ ] Decorative CSS ≤10 lines total incl. comments — no file-header comment (hero wash +
      featured-card ring), `--bulma-*`-derived; no resets — Bulma ships one.
      The ring sets `--bulma-shadow` (via a scoped CSS rule or `Theme bulmaVars`).
- [ ] Set the icon library once via `<ConfigProvider iconLibrary="…">` at the root.
- [ ] Site built? ~800 KB raw / ~82 KB gzip CSS is the expected default-flavor size — to shrink
      it, run the `bestax-optimize` skill (measure first).

Files in this skill

  • SKILL.md30.8 KB
  • examples/app-shell.tsx2.4 KB
  • examples/card-grid.tsx2.6 KB
  • examples/centered.tsx1.4 KB
  • examples/content-page.tsx2.6 KB
  • examples/landing.tsx3.8 KB
  • references/archetypes.md9.9 KB
  • references/layout-components.md25 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in ai-agents →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.