Skip to content

fix(bulma-ui): name custom form controls from a Field label - #974

Merged
allxsmith merged 21 commits into
mainfrom
fix/939-field-label-names
Oct 9, 2026
Merged

allxsmith merged 21 commits into
mainfrom
fix/939-field-label-names

Conversation

@allxsmith

@allxsmith allxsmith commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

A labeled Field gave the id its label points at only to InputBase, SelectBase and TextAreaBase. Every other bestax input drops its own label inside a Field and took nothing in its place. So inside a labeled Field, the Numberinput, Slider, DateInput, TimeInput and DateTimeInput inputs and the Radios and Checkboxes groups had no accessible name. Autocomplete, Taginput, File and Rate were named only by a placeholder or a built-in fallback. axe reported its label rule as critical for the single inputs.

What changes:

  • useAutoLabelId now adopts the surrounding Field's id when the control renders no label of its own, so each single-input control puts it on its real input. A range Slider gives it to the low thumb, as its own label does. Taginput drops its "Add tag" fallback once a label targets the input.
  • Field gives its label an id while the association is on, using the caller's labelProps.id when set. useAutoLabelledBy points a group's aria-labelledby at it, so Radios, Checkboxes and Rate are named by the Field's label, and Rate drops its "Rating" fallback.
  • Precedence stays as it was. An id the caller sets on the control wins, and the label then names it only through labelProps={{ htmlFor }}, as with InputBase. An aria-label or aria-labelledby the caller sets on a group wins, so the Field's aria-labelledby stays off it. A component's own label keeps its own id wherever it renders, and inside a Field it was already dropped, so the two never compete. labelProps.htmlFor on the Field, grouped, hasAddons and a nested unlabeled Field all still turn the association off.
  • An inline picker has no input, so it takes nothing from the Field and derives no ids from it, and neither does a Taginput at its tag limit.
  • DateRangeInput, which landed on main meanwhile, is named as a group: the Field's label names its role="group" root through aria-labelledby, as its own label does, and each input keeps "Start date" or "End date".
  • A caller prop passed as undefined no longer wipes a name: an undefined id leaves each control its label's id, under a Field's label or its own, and an undefined aria-labelledby leaves Rate its fallback.
  • Two controls in one plain labeled Field both adopt its id, which is what two InputBases already do. The docs now say to give each its own id.

The docs follow. The Field accessibility notes and label TSDoc describe the wider reach. Each affected control's label TSDoc and accessibility notes say a surrounding Field's label names it. The Basic Numberinput and Basic Slider examples now have a label. The Context-Aware Rendering examples use a horizontal Field whose body holds an inner Field, which starts its own scope by design, so those examples now wire the label by hand: labelProps={{ htmlFor }} and an id, or labelProps={{ id }} and aria-labelledby for a group. The Field page's File example set a label that the Field drops; it now sets buttonLabel. The bestax-form skill and a contributor note in src/form/CLAUDE.md cover the same ground.

Checked in Chromium with Playwright and axe on a page that holds each control inside a labeled Field and inside Field > Control. On main, the single inputs fail axe's label rule and most controls have no name. On this branch each control takes its name from the Field label, and the label rule passes. File reads the label plus its button text, as it does with its own label.

Left out: the horizontal pattern with an inner Field still needs manual wiring for every control, including Input, Select and TextArea, whose pages this leaves alone. The picker *Base components composed directly in a Field do not adopt the id the way InputBase does.

pnpm all passes locally.

Fixes #939

Summary by CodeRabbit

  • New Features

    • Labels on a Field now automatically provide accessible names for supported single controls and control groups, with caller-provided labels and ARIA naming taking precedence.
    • Updated form examples show how to associate labels explicitly in layouts where automatic association does not apply.
  • Bug Fixes

    • Resolved cases where supplied input IDs could override label associations.
    • Corrected label behavior for controls that do not render an input and for tag inputs with fallback accessible names.
  • Documentation

    • Expanded form accessibility guidance and examples for label association, nested fields, and grouped or addon layouts.

A labeled Field gave its id only to InputBase, SelectBase and TextAreaBase. The other
convenience inputs drop their own label inside a Field and took nothing in its place, so
Numberinput, Slider, DateInput, TimeInput, DateTimeInput and the Radios and Checkboxes groups
had no accessible name there, and Autocomplete, Taginput, File and Rate fell back to a
placeholder or a built-in name.

useAutoLabelId now adopts the surrounding Field's id when the control renders no label of its
own, so each single-input control puts it on its real input. Field gives its label an id while
the association is on, and useAutoLabelledBy points a group's aria-labelledby at it. An id the
caller set on the control still wins, as does an aria-label or aria-labelledby on a group, and
an inline picker, which has no input, takes nothing.

The Field accessibility notes, each control's TSDoc and its Context-Aware section say so. The
horizontal examples there put the control in a nested Field, which starts its own scope, so
they now wire the label by hand. The basic Numberinput and Slider examples get a label.

Fixes #939
@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 42 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: allxsmith/bestax/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: bea7ce32-c87f-462b-abe2-a1fb3185dce8
📥 Commits

Reviewing files that changed from the base of the PR and between 1658ec1 and ddb7c34.

📒 Files selected for processing (58)
  • bestax-mcp/data/catalog.json
  • bestax-mcp/data/components/Autocomplete.json
  • bestax-mcp/data/components/Checkboxes.json
  • bestax-mcp/data/components/DateInput.json
  • bestax-mcp/data/components/DateRangeInput.json
  • bestax-mcp/data/components/DateTimeInput.json
  • bestax-mcp/data/components/Field.json
  • bestax-mcp/data/components/File.json
  • bestax-mcp/data/components/Numberinput.json
  • bestax-mcp/data/components/Radios.json
  • bestax-mcp/data/components/Rate.json
  • bestax-mcp/data/components/Slider.json
  • bestax-mcp/data/components/Taginput.json
  • bestax-mcp/data/components/TimeInput.json
  • bestax-mcp/data/skills.json
  • bulma-ui/src/form/Autocomplete.tsx
  • bulma-ui/src/form/CLAUDE.md
  • bulma-ui/src/form/Checkboxes.tsx
  • bulma-ui/src/form/DateInput.tsx
  • bulma-ui/src/form/DateRangeInput.tsx
  • bulma-ui/src/form/DateTimeInput.tsx
  • bulma-ui/src/form/Field.stories.tsx
  • bulma-ui/src/form/Field.tsx
  • bulma-ui/src/form/File.tsx
  • bulma-ui/src/form/FormContext.tsx
  • bulma-ui/src/form/Input.tsx
  • bulma-ui/src/form/Numberinput.tsx
  • bulma-ui/src/form/Radios.tsx
  • bulma-ui/src/form/Rate.tsx
  • bulma-ui/src/form/Select.tsx
  • bulma-ui/src/form/Slider.tsx
  • bulma-ui/src/form/Taginput.tsx
  • bulma-ui/src/form/TextArea.tsx
  • bulma-ui/src/form/TimeInput.tsx
  • bulma-ui/src/form/__tests__/Autocomplete.test.tsx
  • bulma-ui/src/form/__tests__/Checkboxes.test.tsx
  • bulma-ui/src/form/__tests__/DateRangeInput.test.tsx
  • bulma-ui/src/form/__tests__/Field.test.tsx
  • bulma-ui/src/form/__tests__/Numberinput.test.tsx
  • bulma-ui/src/form/__tests__/Radios.test.tsx
  • bulma-ui/src/form/__tests__/Rate.test.tsx
  • bulma-ui/src/form/__tests__/Taginput.test.tsx
  • bulma-ui/src/form/useAutoLabelId.ts
  • docs/docs/api/form/autocomplete.md
  • docs/docs/api/form/checkboxes.md
  • docs/docs/api/form/datetime/dateinput.md
  • docs/docs/api/form/datetime/daterangeinput.md
  • docs/docs/api/form/datetime/datetimeinput.md
  • docs/docs/api/form/datetime/timeinput.md
  • docs/docs/api/form/field.md
  • docs/docs/api/form/file.md
  • docs/docs/api/form/numberinput.md
  • docs/docs/api/form/radios.md
  • docs/docs/api/form/rate.md
  • docs/docs/api/form/slider.md
  • docs/docs/api/form/taginput.md
  • skills/bestax-form/SKILL.md
  • skills/bestax-form/references/api.md

Walkthrough

Field labels now provide accessible names to eligible single controls and groups. The change adds label context and hook logic, updates form controls and tests, and revises component examples and API guidance.

Changes

Field label association

Layer / File(s) Summary
Field label contexts and resolution
bulma-ui/src/form/FormContext.tsx, bulma-ui/src/form/Field.tsx, bulma-ui/src/form/useAutoLabelId.ts, bulma-ui/src/form/CLAUDE.md
Field provides a label target and label element ID. The hooks use those values to resolve input IDs and group aria-labelledby references.
Control integration and validation
bulma-ui/src/form/*.tsx, bulma-ui/src/form/__tests__/*.test.tsx, bulma-ui/src/form/Field.stories.tsx
Form controls use the resolved label association. Caller-provided IDs and ARIA naming attributes are accounted for. Tests cover single controls, groups, and association exceptions.
Examples and reference updates
bestax-mcp/data/*, docs/docs/api/form/*, skills/bestax-form/*
Examples and API references describe automatic Field label association and cases that require explicit wiring. Catalog version and API reference byte-count metadata are updated.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant Field
  participant FormContext
  participant LabelHooks as useAutoLabelId and useAutoLabelledBy
  participant Controls as Single controls and groups
  Field->>FormContext: Provide label target and label element ID
  FormContext->>LabelHooks: Supply Field label context
  LabelHooks->>Controls: Resolve control ID or aria-labelledby
Loading

Fixed issue severity: Medium

Merge Risk: 🟡 Moderate · up to 1658e

Groups inside labeled Fields can lose their accessible names when a wrapper forwards an undefined aria-labelledby value. Preserve the Field reference in that case before merging.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: naming custom form controls from a surrounding Field label.
Description check ✅ Passed The description provides a detailed summary of the changes, affected form controls, accessibility behavior, documentation updates, testing results, related issue, and known limitations. It does not re…
Linked Issues check ✅ Passed Issue #939 requires a labeled Field to name the affected controls through the real input ID or aria-labelledby, and requires related examples and documentation updates. The changes implement `useA…
Out of Scope Changes check ✅ Passed The changes stay within the Field-label accessibility scope of issue #939. Hook and context changes support label association. Control updates preserve ID and ARIA precedence. Tests and documentation …
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 27 files. (31 skipped: …
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://9e42bb3c.bestax.pages.dev

Comment thread bulma-ui/src/form/Slider.tsx
Comment thread docs/docs/api/form/field.md Outdated
Comment thread bulma-ui/src/form/File.tsx
Comment thread bulma-ui/src/form/Taginput.tsx
@allxsmith

Copy link
Copy Markdown
Owner Author

deep-review: fresh

The last run on 91c9b42 was cancelled at the runner limit after it posted four threads (on Slider.tsx, field.md, File.tsx and Taginput.tsx) but before its summary. Read them, don't post them again, and count each as a row in this review's table. Then carry on with the rest: the mechanism in FormContext.tsx, Field.tsx and useAutoLabelId.ts first, then the call sites in Radios, Checkboxes and Rate, then the tests and docs. The files under bestax-mcp/data/ and the generated props tables come from pnpm gen, and CI's staleness checks hold them to the source.

At its tag limit a Taginput renders no input, yet it still adopted a surrounding Field's id,
against the convention the label hooks set for a mode with nothing to label. It now passes
hasInput the way the inline pickers do, and their guard test covers it too. Nothing renders
differently today; this keeps a future id derived from the control from colliding.

The Field docs no longer say the component pages' horizontal examples are all wired by hand,
since the Input, Select and TextArea pages aren't yet (#968).
Comment thread bulma-ui/src/form/useAutoLabelId.ts Outdated
Comment thread bulma-ui/src/form/useAutoLabelId.ts
@allxsmith

Copy link
Copy Markdown
Owner Author

deep-review: fresh

Two runs on 91c9b42 were cancelled at the runner limit before posting a summary. Between them they posted six threads, on Slider.tsx, field.md, File.tsx, Taginput.tsx and two on useAutoLabelId.ts. Read them, don't post them again, and count each as a row in this review's table. What they haven't covered is FormContext.tsx, Field.tsx and the Radios, Checkboxes and Rate call sites; finish there, then the tests and docs. The files under bestax-mcp/data/ and the generated props tables come from pnpm gen, and CI's staleness checks hold them to the source.

… hand

A Taginput dropped its "Add tag" aria-label only when an id its Field generated tied a label to
it. With labelProps={{ htmlFor }} on the Field and a matching id, the pattern the horizontal
examples teach, the fallback stayed and outranked the label, so the input still announced
"Add tag". Field now passes down what its label's for actually points at, and an unlabeled Field
passes on its parent's, so the hook sees a hand-wired label across the inner Field too. It only
compares that target with the control's own id and never hands it out.
Comment thread bulma-ui/src/form/useAutoLabelId.ts Outdated
Comment thread bulma-ui/src/form/useAutoLabelId.ts

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep review — 4 blocking · 4 advisory

# Severity Area Finding Location
1 🟠 Major Accessibility The labelProps={{ htmlFor }} + id pattern the docs and skill now prescribe cannot name a Taginput — labelled is false, so aria-label="Add tag" stays and outranks the label[for] bulma-ui/src/form/useAutoLabelId.ts:70
2 🟡 Minor Accessibility Two pickers in one labeled Field now collide on aria-controls: popoverId derives from the shared adopted id, where it used to be per-instance bulma-ui/src/form/useAutoLabelId.ts:58
3 🟡 Minor Accessibility A caller aria-labelledby={undefined} is falsy for callerNamed yet still wins the spread, so it cancels the Field association — and Rate loses its "Rating" fallback with it, going from named to nameless bulma-ui/src/form/useAutoLabelId.ts:124
4 🟡 Minor Accessibility Six of the nine newly-named controls (Numberinput, both Slider thumbs, File, the three pickers) lose the adopted id to an own-but-undefined id key, because id={controlId} sits before the props spread bulma-ui/src/form/useAutoLabelId.ts:59
5 🔵 Advisory Accessibility A range Slider inside a labeled Field is still not named by it: both thumbs keep an unconditional aria-label, which outranks the label[for] the low thumb now resolves bulma-ui/src/form/Slider.tsx:61
6 🔵 Advisory Accessibility <Field label><File/></Field> now gives the file input two associated labels (the Field's plus the wrapping file-label), which is what form-field-multiple-labels fires on bulma-ui/src/form/File.tsx:35
7 🔵 Advisory Robustness Taginput at maxTags adopts the Field's id with no input to put it on — it is the one single-input control that renders none in a reachable mode yet does not pass hasInput: false bulma-ui/src/form/Taginput.tsx:281
8 🔵 Advisory Accessibility "as the component pages' horizontal examples do" is not true of input.md, select.md or textarea.md, whose six horizontal examples still render an unnamed control docs/docs/api/form/field.md:495

Overall: The core design is sound and lands #939 properly: useAutoLabelId adopting the Field's id when the control renders no label of its own, plus a second context carrying the label element's own id for the three groups, is the minimal shape that reaches every control, and the precedence rules (caller id wins, caller ARIA wins, grouped/hasAddons/nested-Field opt out, inline pickers take nothing) are all pinned by tests. pnpm --filter @allxsmith/bestax-bulma exec jest src/form passes (42 suites, 2496 tests) on this branch, and gen:mcp:check and check:conformance are both green, so the generated index and props tables match their sources. The riskiest part is not the hook but the ordering at the call sites: three of the four blocking findings are the same root shape — an attribute the new code computes is written before a props spread that can carry the same key with an undefined value, which silently wins. InputBase.tsx:95-98 already carries a comment naming that exact hazard, so the pattern to copy is in the repo. Start a human review with findings 3 and 4 (they decide whether the fix actually reaches the six controls that matter most), then finding 1, which is the one case where the escape hatch the new docs and the shipped bestax-form skill prescribe produces a wrong name rather than no name.

Residual risk:

  • Controls still nameless inside a labeled Field. Refuted for the eleven the PR covers by the new it.each(singles)/it.each(groups) matrices in Field.test.tsx, which assert the accessible name (getByLabelText / getByRole(role, { name })), not just a matching for — except through findings 3 and 4, which are posted. Checkbox/Switch and inline pickers take no association by design and field.md:497 says so.
  • Ids derived from the adopted id. Swept src/form/*.tsx for ${id}- templates: only the three pickers' popoverId derives one, which is finding 2. Field's own ${targetId}-label cannot collide with it, and the leaves an inline picker alone test pins the hasInput: false path through [id^="${target}"]:not(label).
  • Duplicate ids from two controls in one Field. Open and accepted: field.md:496 and the label TSDoc tell the caller to give each control an id of its own. Nothing enforces it, and finding 2 is the sharper consequence of ignoring it.
  • The nested-Field scope reset. Refuted: Field re-provides both contexts with its own targetId, so an inner Field resets them, and a nested unlabeled Field shadows the outer label for groups too pins it for the new context as well as the old.
  • hasInput: false coverage. Open, low: only DateInput inline is exercised inside a labeled Field; TimeInput/DateTimeInput inline ride the same shared hook, so the exposure is a future divergence rather than a defect today.
  • Hydration. Refuted: the label's id and every adopted id come from useId through the same render path as #495's, so server and client agree; there is no clock or random input anywhere in the diff.

🏄 Clean set, honestly — paddled out and actually caught the wave #939 was pointing at, with eleven controls finally announcing their own names instead of mumbling "Add tag". Just watch the shorebreak where {...rest} crashes over the id you just set: tidy that up and this one rides all the way in.

@allxsmith

Copy link
Copy Markdown
Owner Author

deep-review: verify

Every open thread has a reply: fixed, with the commit, or refuted with a reason. This settles them on ddb7c34.

@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://fe066531.bestax.pages.dev

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep review (verify) — 4 resolved · 0 open

# Severity Area Thread Location Outcome
1 🟡 Minor API "and the like" over-claims: Switch/Checkbox/Radio adopt nothing bulma-ui/src/form/Field.tsx:33 ✅ Verified fixed
2 🔵 Advisory Accessibility Listbox name degrades to "Suggestions" inside a labeled Field bulma-ui/src/form/Autocomplete.tsx:45 ✅ Accepted — tracked in #998
3 🔵 Advisory API Caller aria-label beat a Field's label but lost to the group's own label bulma-ui/src/form/useAutoLabelId.ts:138 ✅ Verified fixed
4 🔵 Advisory Accessibility Six group examples emit a for that matches nothing docs/docs/api/form/checkboxes.md:83 ✅ Verified fixed (+ Field itself tracked in #1004)

Overall: This pass settled threads and reviewed no commits. All four of my open threads are now resolved: two on code/docs the author fixed in c68fb50a and 4c319bb2 (regenerated in 3b940c78), and two refutations that hold — #998 scopes the Autocomplete listbox name and names this PR as its blocker, and #1004 scopes Field's unmatched for along with the SSR question it raises. The human should focus on the callerNamed reordering in useAutoLabelId.ts:137-142, since it changes #494's own-label precedence for all four groups; the new it.each rows covering both aria props against each group's own label are the evidence that it landed intentionally.

Residual risk: not assessed — a verify pass settles the prior review's findings and raises nothing new. Ask for deep-review: fresh to have the current code reviewed.

What I checked
  • Field.tsx:33 — hedge gone, full adopting set enumerated, Checkbox/Radio/Switch called out as taking nothing; propagated to docs/docs/api/form/field.md:528 and bestax-mcp/data/components/Field.json:152.
  • useAutoLabelId.ts:137-142 — callerNamed now gates outside the active ternary, exactly the shape suggested.
  • checkboxes.md:86/:114, radios.md:88/:116, rate.md:220/:244 — all six carry labelProps={{ id, htmlFor: undefined }}; no labelProps with an id and no htmlFor remains in those pages or daterangeinput.md.
  • Field.test.tsx:775-784 (the three take nothing, still named by children), :870-895 (both aria props vs. own label, per group), :898-910 (Rate keeps "Rating" on a forwarded undefined), :978-999 (hand-wired recipe: id kept, no for, group named).
  • gh api .../issues/998 and .../1004 — both open, both scoping the deferred work.
  • pnpm --filter @allxsmith/bestax-bulma exec jest src/form — 46 suites, 2811 tests, all passing.

🏄 Four threads paddled out, four came back in clean — two real fixes, two honest "that's a different wave, already caught it in #998 and #1004." No chop left on this one, it's good to go.

@github-actions github-actions Bot added the review-converged Deep review converged: no blocking findings, all review threads resolved, checks green label Oct 9, 2026
@allxsmith
allxsmith merged commit fccb526 into main Oct 9, 2026
83 checks passed
@allxsmith
allxsmith deleted the fix/939-field-label-names branch October 9, 2026 00:39
@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 5.27.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 1.14.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

allxsmith added a commit that referenced this pull request Oct 9, 2026
… range Slider thumbs

An `aria-label` or `aria-labelledby` passed to a range Slider reached only the
low thumb, through the rest spread, so it renamed that thumb while the high one
kept the label's name: `<Slider range label="Price" aria-label="Budget floor" />`
read "Budget floor" and "Price Maximum value", two unrelated names for one
widget.

The Slider now routes those two attributes itself instead of spreading them.
Either one names the Slider in the label's place, so both thumbs read it before
their own text: "Budget Minimum value" and "Budget Maximum value". An
`aria-labelledby` reaches the thumbs the way the label does, through
`aria-labelledby` and the hidden span, and an `aria-label` is joined to each
thumb's text. A non-empty `aria-labelledby` wins over an `aria-label`, as it
does in the browser. An `ariaLabel` entry is still its thumb's whole name and
wins over both.

The presence rule from groups (#974) carries over: any `aria-labelledby` key,
even an undefined one, takes the naming over, so `aria-labelledby={undefined}`
keeps every label out and leaves both thumbs "Minimum value" and "Maximum
value". An empty `aria-label` still counts as unset.

Single-thumb Sliders keep spreading the caller's attributes as before.
allxsmith added a commit that referenced this pull request Oct 9, 2026
The range Slider decides whether the caller named it the way a group does
(#974): any `aria-labelledby` key counts, even an undefined one, while an
`aria-label` counts only when it is non-empty. Every test so far passed a
defined `aria-labelledby`, which a plain truthiness check satisfies just as
well, so swapping the presence test for one would have kept the suite green
while dropping the opt-out.

Pin both sides. `aria-labelledby={undefined}`, under the Slider's own label and
under a surrounding Field's, leaves both thumbs "Minimum value" and "Maximum
value" with no `aria-labelledby` and no hidden spans, while the label's `for`
still lands on the low thumb. An empty `aria-label` leaves the label in both
names.
@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 2.25.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

allxsmith added a commit that referenced this pull request Oct 9, 2026
… range Slider thumbs

An `aria-label` or `aria-labelledby` passed to a range Slider reached only the
low thumb, through the rest spread, so it renamed that thumb while the high one
kept the label's name: `<Slider range label="Price" aria-label="Budget floor" />`
read "Budget floor" and "Price Maximum value", two unrelated names for one
widget.

The Slider now routes those two attributes itself instead of spreading them.
Either one names the Slider in the label's place, so both thumbs read it before
their own text: "Budget Minimum value" and "Budget Maximum value". An
`aria-labelledby` reaches the thumbs the way the label does, through
`aria-labelledby` and the hidden span, and an `aria-label` is joined to each
thumb's text. A non-empty `aria-labelledby` wins over an `aria-label`, as it
does in the browser. An `ariaLabel` entry is still its thumb's whole name and
wins over both.

The presence rule from groups (#974) carries over: any `aria-labelledby` key,
even an undefined one, takes the naming over, so `aria-labelledby={undefined}`
keeps every label out and leaves both thumbs "Minimum value" and "Maximum
value". An empty `aria-label` still counts as unset.

Single-thumb Sliders keep spreading the caller's attributes as before.
allxsmith added a commit that referenced this pull request Oct 9, 2026
The range Slider decides whether the caller named it the way a group does
(#974): any `aria-labelledby` key counts, even an undefined one, while an
`aria-label` counts only when it is non-empty. Every test so far passed a
defined `aria-labelledby`, which a plain truthiness check satisfies just as
well, so swapping the presence test for one would have kept the suite green
while dropping the opt-out.

Pin both sides. `aria-labelledby={undefined}`, under the Slider's own label and
under a surrounding Field's, leaves both thumbs "Minimum value" and "Maximum
value" with no `aria-labelledby` and no hidden spans, while the label's `for`
still lands on the low thumb. An empty `aria-label` leaves the label in both
names.
bestax-release-bot Bot pushed a commit that referenced this pull request Oct 9, 2026
## [5.27.4](https://github.com/allxsmith/bestax/compare/@allxsmith/bestax-bulma@5.27.3...@allxsmith/bestax-bulma@5.27.4) (2026-10-09)

### Bug Fixes

* **bulma-ui:** let a caller's aria-label or aria-labelledby name both range Slider thumbs ([047ff7c](047ff7c)), closes [#974](#974)
* **bulma-ui:** name each range Slider thumb from its label ([6b962e3](6b962e3)), closes [#981](#981)
* **create-bestax:** announce the starter's notifications through a status region ([91dbfd5](91dbfd5))
* **create-bestax:** give each starter notification a status region of its own ([f1ef4df](f1ef4df))
* **create-bestax:** keep focus on the counter when Reset disables itself ([733e505](733e505))
* **create-bestax:** keep the reason for the stylesheet order in every scaffolded entry file ([f4591e0](f4591e0))
* **create-bestax:** return focus when the starter's notification closes, and tighten guards ([67e3678](67e3678))
* **create-bestax:** say which helper props render nothing under the no-helpers flavors ([b29985b](b29985b))
* **create-bestax:** stop the no-helpers CLAUDE.md offering helper props as the way out ([a96c203](a96c203))
* **create-bestax:** stop the vite-ts build shadowing its config, and fix the starter page ([1dd964d](1dd964d))
bestax-release-bot Bot pushed a commit that referenced this pull request Oct 9, 2026
## [4.2.15](https://github.com/allxsmith/bestax/compare/create-bestax@4.2.14...create-bestax@4.2.15) (2026-10-09)

### Bug Fixes

* **bestax-mcp:** document Theme's variable props, point at the prose page, name the title rule ([e72aee0](e72aee0))
* **bestax-mcp:** give Theme, ConfigProvider, Portal and ClientOnly prop tables ([fddfd65](fddfd65))
* **bestax-mcp:** make the links in markdown the index ships absolute ([fd134fa](fd134fa))
* **bestax-mcp:** mark is-empty rendered by File in its lookup row, not kept as a class ([e53ee6c](e53ee6c))
* **bestax-mcp:** match bestax.io links with an anchored pattern, not a substring ([f210570](f210570))
* **bestax-mcp:** point at a component's page only under a table it can mean ([e45b4b8](e45b4b8))
* **bestax-mcp:** read an overloaded component's implementation, not its first signature ([67f1552](67f1552))
* **bestax-mcp:** resolve links in every string the index ships, and refuse an empty prose title ([e620347](e620347))
* **bestax-mcp:** say that File renders is-empty beside has-name with no name ([590288c](590288c))
* **bestax-mcp:** tag the bestax.io links inside index answers, like the footer's ([d6d4c57](d6d4c57))
* **bestax-mcp:** tell the File lookup to pin an empty name with fileName="" ([188feb0](188feb0))
* **bestax-migrate:** convert a has-name .file with no name only with is-empty ([58a73dd](58a73dd))
* **bestax-migrate:** pin an empty File name so a pick leaves the converted markup as it was ([8bf99d5](8bf99d5))
* **bestax-migrate:** say in the File is-empty TODO that the conversion pins fileName="" ([b837d62](b837d62))
* **bestax-migrate:** say the File is-empty TODO wants the class written, not a condition ([5330608](5330608))
* **bulma-ui:** drop Taginput's fallback name when a label is wired by hand ([2cb9366](2cb9366))
* **bulma-ui:** keep a hasName File's CTA corners rounded while there is no name ([d152cd4](d152cd4))
* **bulma-ui:** keep Input, Select and TextArea labelled through an undefined id ([1658ec1](1658ec1))
* **bulma-ui:** keep the label's id and name through an undefined caller prop ([8fd3457](8fd3457))
* **bulma-ui:** let a caller's aria name win over a group's own label ([c68fb50](c68fb50))
* **bulma-ui:** let a caller's aria-label or aria-labelledby name both range Slider thumbs ([047ff7c](047ff7c)), closes [#974](#974)
* **bulma-ui:** name custom form controls from a Field label ([91c9b42](91c9b42)), closes [#939](#939)
* **bulma-ui:** name DateRangeInput's default icon in its Control-level warning ([914b92a](914b92a)), closes [#952](#952)
* **bulma-ui:** name DateRangeInput's default icon in its Control-level warning ([30c6933](30c6933)), closes [#952](#952)
* **bulma-ui:** name each range Slider thumb from its label ([6b962e3](6b962e3)), closes [#981](#981)
* **bulma-ui:** stop crediting Bulma's is-empty with a boxed File's round corners ([ebb252b](ebb252b))
* **bulma-ui:** take no Field id for a Taginput at its tag limit ([04b3575](04b3575)), closes [#968](#968)
* **create-bestax:** announce the starter's notifications through a status region ([91dbfd5](91dbfd5))
* **create-bestax:** give each starter notification a status region of its own ([f1ef4df](f1ef4df))
* **create-bestax:** keep focus on the counter when Reset disables itself ([733e505](733e505))
* **create-bestax:** keep the reason for the stylesheet order in every scaffolded entry file ([f4591e0](f4591e0))
* **create-bestax:** return focus when the starter's notification closes, and tighten guards ([67e3678](67e3678))
* **create-bestax:** say which helper props render nothing under the no-helpers flavors ([b29985b](b29985b))
* **create-bestax:** stop the no-helpers CLAUDE.md offering helper props as the way out ([a96c203](a96c203))
* **create-bestax:** stop the vite-ts build shadowing its config, and fix the starter page ([1dd964d](1dd964d))
@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 4.2.15 🎉

The release is available on:

Your semantic-release bot 📦🚀

bestax-release-bot Bot pushed a commit that referenced this pull request Oct 10, 2026
## [1.14.4](https://github.com/allxsmith/bestax/compare/bestax-mcp@1.14.3...bestax-mcp@1.14.4) (2026-10-10)

### Bug Fixes

* **bestax-mcp:** release with every bulma-ui release so the published index keeps up ([#1027](#1027)) ([618e483](618e483))
* **bulma-ui:** accept material-symbols 0.47 in the peer range ([539cca2](539cca2))
* **bulma-ui:** drop a Field label's for when nothing it holds takes it ([#1020](#1020)) ([3604c14](3604c14))
* **bulma-ui:** keep a Notification's text clear of its close button ([#1017](#1017)) ([91ebb63](91ebb63))
* **bulma-ui:** keep Theme's component-variable check out of production ([537d90d](537d90d))
* **bulma-ui:** let a caller's aria-label or aria-labelledby name both range Slider thumbs ([047ff7c](047ff7c)), closes [#974](#974)
* **bulma-ui:** name a composed DateRangeInputBase's group from a Field label ([75aa6cb](75aa6cb))
* **bulma-ui:** name a range Slider's thumbs from a hand-wired Field label's id ([355065b](355065b))
* **bulma-ui:** name Autocomplete's list from a hand-wired Field label's id ([ad46c06](ad46c06))
* **bulma-ui:** name Autocomplete's suggestion list from a surrounding Field's label ([190183f](190183f)), closes [#998](#998)
* **bulma-ui:** name each range Slider thumb from its label ([6b962e3](6b962e3)), closes [#981](#981)
* **bulma-ui:** name the date and time picker bases from a Field label ([e7ae797](e7ae797))
* **bulma-ui:** say a label placed in Field.Label reaches no thumbs or list ([07c2a99](07c2a99))
* **bulma-ui:** say FieldLabel renders the label column, not a label ([2a6ab10](2a6ab10))
* **bulma-ui:** say the label prop needs wiring to reach thumbs or list in an inner Field ([9b33d8d](9b33d8d))
* **bulma-ui:** say which controls a hand-wired Field label's id reaches ([514127f](514127f))
* **bulma-ui:** warn when Theme is given a variable Bulma sets on the component ([bb8f9b8](bb8f9b8)), closes [#1021](#1021)
* **create-bestax:** announce the starter's notifications through a status region ([91dbfd5](91dbfd5))
* **create-bestax:** give each starter notification a status region of its own ([f1ef4df](f1ef4df))
* **create-bestax:** keep focus on the counter when Reset disables itself ([733e505](733e505))
* **create-bestax:** keep the reason for the stylesheet order in every scaffolded entry file ([f4591e0](f4591e0))
* **create-bestax:** pin material-symbols to the newest range bestax-bulma accepts ([ff4f820](ff4f820))
* **create-bestax:** return focus when the starter's notification closes, and tighten guards ([67e3678](67e3678))
* **create-bestax:** say which helper props render nothing under the no-helpers flavors ([b29985b](b29985b))
* **create-bestax:** stop the no-helpers CLAUDE.md offering helper props as the way out ([a96c203](a96c203))
* **create-bestax:** stop the vite-ts build shadowing its config, and fix the starter page ([1dd964d](1dd964d))
* **docs:** give the mixin note the modifier caveat its lead carries ([1427f08](1427f08))
* **docs:** keep the modifier caveat for mixin variables in the MCP index ([e227663](e227663))
* **docs:** name the one edge of the rule for which variables Theme reaches ([b33ebfa](b33ebfa))
* **docs:** qualify the guide and the theming skill on what Theme reaches ([9c2347b](9c2347b))
* **docs:** read only a component's own mixin as its home ([f40cd2c](f40cd2c))
* **docs:** refuse a CSS-variable scope the page cannot fully word ([c815602](c815602))
* **docs:** say where component-scoped Bulma variables have to be set ([771d5bd](771d5bd)), closes [#1021](#1021)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deep-review needs-human-review Loop converged (or contested): awaiting owner review + manual merge released review-converged Deep review converged: no blocking findings, all review threads resolved, checks green

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A Field label doesn't name most custom form controls

1 participant