Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
fdd90fe
fix(components): make RadioGroup an accessible radiogroup that keeps …
owjs3901 Oct 2, 2026
465adca
fix(components): label Checkbox with the id it is given and pin test ids
owjs3901 Oct 2, 2026
c274eab
fix(components): make Toggle an accessible switch
owjs3901 Oct 2, 2026
3d87be9
fix(components): make Select an accessible listbox with keyboard support
owjs3901 Oct 2, 2026
00dd003
test(components): pin Checkbox and nested Select ids so snapshots ign…
owjs3901 Oct 2, 2026
c1273ad
Merge remote-tracking branch 'origin/fix/ui-kit-forward-ref' into fix…
owjs3901 Oct 3, 2026
f290cc8
Merge remote-tracking branch 'origin/fix/radio-group-a11y' into fix/u…
owjs3901 Oct 3, 2026
4aa4785
Merge remote-tracking branch 'origin/fix/toggle-switch' into fix/ui-k…
owjs3901 Oct 3, 2026
2d36d67
Merge remote-tracking branch 'origin/fix/select-a11y' into fix/ui-kit…
owjs3901 Oct 3, 2026
48fc98f
fix(components): make the UI kit, theme helpers and reset-css behave …
owjs3901 Oct 3, 2026
714539d
fix(components): call the Stepper setter as a member so a minified na…
owjs3901 Oct 3, 2026
d890376
fix(components): name Select options, container, trigger and confirm …
owjs3901 Oct 3, 2026
dabb7d4
Merge main a935315c prerequisite into fix/radio-group-a11y
owjs3901 Oct 8, 2026
26866af
Merge main b711ed1f into fix/radio-group-a11y
owjs3901 Oct 8, 2026
6e29afb
Merge main 9a953607 into fix/radio-group-a11y
owjs3901 Oct 8, 2026
b7c436b
Merge main db631c6c into fix/radio-group-a11y
owjs3901 Oct 8, 2026
b17ed50
Merge main 11790bef into fix/radio-group-a11y
owjs3901 Oct 8, 2026
ca82b67
Merge main a935315c prerequisite into fix/toggle-switch
owjs3901 Oct 8, 2026
5c88b4e
Merge main b711ed1f into fix/toggle-switch
owjs3901 Oct 8, 2026
594861a
Merge main 9a953607 into fix/toggle-switch
owjs3901 Oct 8, 2026
261eef3
Merge main db631c6c into fix/toggle-switch
owjs3901 Oct 8, 2026
a0f0973
Merge main 11790bef into fix/toggle-switch
owjs3901 Oct 8, 2026
4c7b237
Merge main a935315c prerequisite into fix/select-a11y
owjs3901 Oct 8, 2026
79a4a42
Merge main b711ed1f into fix/select-a11y
owjs3901 Oct 8, 2026
cb59592
Merge main 9a953607 into fix/select-a11y
owjs3901 Oct 8, 2026
79b6057
Merge main db631c6c into fix/select-a11y
owjs3901 Oct 8, 2026
03fcadd
Merge main 11790bef into fix/select-a11y
owjs3901 Oct 8, 2026
c7e4ac1
Merge updated #720 into fix/ui-kit-behavior
owjs3901 Oct 8, 2026
41843f9
Merge updated #721 into fix/ui-kit-behavior
owjs3901 Oct 8, 2026
d9b73a7
Merge updated #723 into fix/ui-kit-behavior
owjs3901 Oct 8, 2026
ebada76
Merge updated #724 into fix/ui-kit-behavior
owjs3901 Oct 8, 2026
a30fbb7
Merge updated #725 into fix/ui-kit-behavior
owjs3901 Oct 8, 2026
a0418cc
chore: merge main into ui kit behavior
owjs3901 Oct 14, 2026
a464f6c
chore: merge main b4c143b1 into #724
owjs3901 Oct 17, 2026
09e12da
fix(components): use the $primary theme token for the Toggle focus ring
owjs3901 Oct 17, 2026
f4aeeec
fix(components): merge main into radio group accessibility
owjs3901 Oct 17, 2026
0ef7484
fix(components): merge main into select accessibility
owjs3901 Oct 17, 2026
49de18b
fix(components): merge updated radio accessibility parent
owjs3901 Oct 17, 2026
c736961
fix(components): merge reviewed toggle switch parent
owjs3901 Oct 17, 2026
dfdf810
fix(components): merge updated select accessibility parent
owjs3901 Oct 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_radio_group_a11y.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"packages/components/package.json": "Patch"
},
"note": "RadioGroup is a radiogroup with an optional label, its radios share a name (generated when not given) so arrow keys move between them, and it keeps the typed values of its options: false, 0 and '' stay selectable and onChange receives the option's own value rather than a string. Button-variant radios are visually hidden instead of display:none, so they stay focusable and show a focus ring, and the default radio's focus outline is valid CSS and shown on :focus-visible. Checkbox uses an id it is given for its labels as well as its input, where the labels used to point at a generated id",
"date": "2026-10-01T00:00:00.000Z"
}
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_select_a11y.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"packages/components/package.json": "Patch"
},
"note": "Select follows the listbox pattern: the trigger has aria-haspopup, aria-controls and opens with ArrowDown/ArrowUp; the popup is a listbox (aria-multiselectable for checkbox selects) whose options carry role=option, aria-selected and aria-disabled; opening focuses the selected or first option, arrows/Home/End move between enabled options, Enter/Space choose one, and Escape or choosing closes the popup and returns focus to the trigger. An outside click on a controlled select now calls onOpenChange(false) instead of changing hidden internal state",
"date": "2026-10-01T00:00:00.000Z"
}
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_toggle_switch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"packages/components/package.json": "Patch"
},
"note": "Toggle is an accessible switch: role=switch with aria-checked, focusable with a visible focus ring, toggled by Space and Enter, labelled through aria-label or aria-labelledby, and submitted under the name it is given; when disabled it leaves the tab order, ignores keys and its hidden input is disabled",
"date": "2026-10-01T00:00:00.000Z"
}
9 changes: 9 additions & 0 deletions .changepacks/changepack_log_ui_kit_behavior.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"changes": {
"packages/components/package.json": "Patch",
"packages/react/package.json": "Patch",
"packages/reset-css/package.json": "Patch"
},
"note": "Components behave for keyboard, screen readers and both React 18 and 19: Button, Input and Textarea no longer force generic aria-labels (the visible text or an associated label names them), Input and Textarea mark errors with aria-invalid and aria-describedby pointing at the message, a loading Button sets aria-busy, is disabled by default (disableWhileLoading) and announces loadingLabel, Input clear dispatches a real change event instead of a fabricated one, color customization variables sit on the container so every part sees them and every public color key matches its CSS custom property (new keys inputDisabledBackground, inputDisabledText, inputPlaceholder, primaryBackground, Textarea disabledBackground and disabledText), Select positions against the viewport and clears the opposite inset, SelectTrigger asChild composes the child onClick, onKeyDown and ref with its own, Stepper counts without a controlled value, keeps its bound and disabled safeguards and renders its text mode as an output element. Select no longer forces aria-labels over its trigger, options, confirm button and container: an option is named by its content, the listbox by its trigger (aria-labelledby) or by what the caller passes. ThemeScript takes nonce and other script props and never aborts on blocked storage or missing matchMedia, follows the system while no theme was chosen and themes saved in other tabs; getTheme, setTheme and initTheme are safe on the server and initTheme returns a function that stops following. reset-css clips accessible-hidden content as sanitize.css does. No new build errors.",
"date": "2026-10-01T00:00:00.000Z"
}
37 changes: 36 additions & 1 deletion packages/components/src/__tests__/forward-ref.browser.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,44 @@ import { describe, expect, it } from 'bun:test'
import { render } from 'bun-test-env-dom'
import { createRef } from 'react'

import { Button, Input, Radio, Textarea } from '../index'
import { Button, Checkbox, Input, Radio, Textarea } from '../index'

describe('forwardRef', () => {
it('links both labels to the forwarded checkbox when a custom id is given', () => {
// Given
const ref = createRef<HTMLInputElement>()

// When
const { container } = render(
<Checkbox ref={ref} id="custom-checkbox">
Checkbox
</Checkbox>,
)

// Then
expect(ref.current?.id).toBe('custom-checkbox')
const labels = container.querySelectorAll('label')
expect(labels).toHaveLength(2)
for (const label of labels) {
expect(label.htmlFor).toBe(ref.current?.id)
}
})

it('hands its ref to the native radio when the button variant is used', () => {
// Given
const ref = createRef<HTMLInputElement>()

// When
render(
<Radio ref={ref} variant="button">
Radio
</Radio>,
)

// Then
expect(ref.current?.type).toBe('radio')
})

it('hands each component ref to its form element', () => {
const button = createRef<HTMLButtonElement>()
const input = createRef<HTMLInputElement>()
Expand Down
58 changes: 29 additions & 29 deletions packages/components/src/__tests__/index.browser.test.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import { describe, expect, it } from 'bun:test'
describe('export', () => {
it('should export components', async () => {
const index = await import('../index')
expect({ ...index }).toEqual({
Button: expect.any(Object),
Input: expect.any(Object),
Textarea: expect.any(Object),
Stepper: expect.any(Function),
Select: expect.any(Function),
Radio: expect.any(Object),
RadioGroup: expect.any(Function),
SelectContainer: expect.any(Function),
SelectDivider: expect.any(Function),
SelectOption: expect.any(Function),
SelectTrigger: expect.any(Function),
StepperContainer: expect.any(Function),
StepperDecreaseButton: expect.any(Function),
StepperIncreaseButton: expect.any(Function),
StepperInput: expect.any(Function),
useStepper: expect.any(Function),
SelectContext: expect.any(Object),
useSelect: expect.any(Function),
Toggle: expect.any(Function),
Checkbox: expect.any(Object),
})
})
})
import { describe, expect, it } from 'bun:test'

describe('export', () => {
it('should export components', async () => {
const index = await import('../index')
expect({ ...index }).toEqual({
Button: expect.any(Object),
Input: expect.any(Object),
Textarea: expect.any(Object),
Stepper: expect.any(Function),
Select: expect.any(Function),
Radio: expect.any(Object),
RadioGroup: expect.any(Function),
SelectContainer: expect.any(Function),
SelectDivider: expect.any(Function),
SelectOption: expect.any(Function),
SelectTrigger: expect.any(Object),
StepperContainer: expect.any(Function),
StepperDecreaseButton: expect.any(Function),
StepperIncreaseButton: expect.any(Function),
StepperInput: expect.any(Function),
useStepper: expect.any(Function),
SelectContext: expect.any(Object),
useSelect: expect.any(Function),
Toggle: expect.any(Function),
Checkbox: expect.any(Object),
})
})
})
Loading
Loading