CLAUDE.md@bulma-ui/src/form · git:20261010.3604c14 · 2026-10-10 · sha256 c8dd0514fc95152b
CLAUDE.md@bulma-ui/src/form git:20261010.3604c14A
Immutable. This exact content never changes and is served at /api/v1/blob/c8dd0514fc95152b.
# form/ — form controls Bulma's Field/Control composition model: `Field` wraps label + control + help text; `Control` wraps a single input and handles icons/loading. Inputs compose inside them — there is **no form library** and none should be added; validation is userland (`skills/bestax-form` teaches the pattern). **Belongs here?** Anything a user types into or selects with. Display widgets go in `../components/`. Conventions: - `FormContext.tsx` is presence detection, not state: `useInsideField`/`useInsideControl` let an input skip rendering its own Field/Control wrapper when already inside one, and it backs group components (Radios). Preserve that skip-if-wrapped behavior in new inputs. Decide the Field with `rendersOwnField` rather than `!insideField` alone: `Control` provides no field context, so that test puts a `.field` inside a bare `.control` (#905). Pass it every prop that changes the Field's own markup, and add a new one to its list, or a bare `Control` silently drops it; a new wrapper also joins the table in `__tests__/bare-control.test.tsx`. `Autocomplete` and `Numberinput` still decide with `!insideField`, because the helper cannot fix them: Autocomplete's own markup carries a `.control`, and Numberinput's root is itself a `.field`, so either one still nests inside a bare `Control` with no Field of its own. Bringing them in means changing that markup first. Decide the Control with `rendersOwnControl` the same way: pass it every prop you hand your own `Control`, as the caller gave it rather than after a default, so that inside a caller's `Control`, where those props do nothing, it can warn about the ones set (#921). A picker passes its `inline` too, since inline it renders no Control in either place. A new Control-level prop joins `ControlLevelProps`, and `bare-control.test.tsx` fails until it does. - A labeled `Field` names the one control it holds (#495, #939), and a new input keeps that working by going through the label hooks in `useAutoLabelId.ts` rather than wiring ids itself. One that renders its own input calls `useAutoLabelId` and puts `controlId` on that input after any props spread, where an undefined `id` key would wipe it, passing `hasInput: false` in a mode that renders none, so nothing derives ids from the Field's. A group calls `useAutoLabelledBy` with its remaining props as `callerProps`, so an `aria-label` or `aria-labelledby` the caller set still wins, and puts `ariaLabelledBy` on its group element. Once the Field's content has mounted, its label keeps the generated `for` only while some content has reported holding the Field's id, so `useAutoLabelId` reports when the control took it (#1004). Anything new that puts that id on an element takes it through `useFieldLabelTarget` or `useAutoLabelId`, which report it, and never reads `useFieldLabelId` itself, or the label drops the `for` that names it. `__tests__/field-label-readers.test.ts` fails on a module outside its `READERS` list that reads it. Content that takes no id, a group included, has nothing to report. - `*Base.tsx` files (`InputBase`, `SelectBase`, `DateInputBase`, `TimeInputBase`, …) are the raw controls without the Field/Control wrapping — deliberately exported from `src/index.ts` as escape hatches, so they are public API too. A base with a single input of its own takes its id from `useFieldLabelTarget`, which hands it the Field's id when the caller set none and tells the Field when it did, as `InputBase` does, so a labeled `Field` names it when composed by hand (#968). A picker base passes `!inline` as its `hasInput`, since inline it renders no input. A base that is a group, as `DateRangeInputBase` is, calls `useAutoLabelledBy` with no label of its own and puts `ariaLabelledBy` on its group in every mode, `inline` too, so a labeled `Field` names it as it names the wrapper (#1005). - Basic inputs (Input, Select, TextArea, …) ship no CSS, but more of this folder has SCSS than you'd guess: even Checkbox and Radio have themed partials, File has one for its keyboard focus ring and a boxed CTA's corners, and every extended input (Autocomplete, DateInput, Numberinput, Rate, Slider, Switch, Taginput, …) does too. Check `../scss/form/_index.scss` for the authoritative list before changing visuals. Follow the anatomy rule in `bulma-ui/CLAUDE.md` (test + story + docs page + export + catalog).