diff --git a/apps/www/src/content/docs/components/accordion/index.mdx b/apps/www/src/content/docs/components/accordion/index.mdx
index db12ac4fb..53cae5139 100644
--- a/apps/www/src/content/docs/components/accordion/index.mdx
+++ b/apps/www/src/content/docs/components/accordion/index.mdx
@@ -107,4 +107,5 @@ Every rendered part carries a stable `data-slot` attribute for [styling and test
- Trigger elements use `aria-expanded` to indicate open/closed state
- Supports keyboard navigation with Enter and Space to toggle items
- Content panels use `aria-controls` and `aria-labelledby` for association
+- A disabled item's trigger has the native `disabled` attribute, so it is left out of the Tab order
- Respects motion preferences: icon rotation and panel expand/collapse motion is enabled only when `prefers-reduced-motion: no-preference`
diff --git a/apps/www/src/content/docs/components/collapsible/index.mdx b/apps/www/src/content/docs/components/collapsible/index.mdx
index 694af2e68..009a11e2f 100644
--- a/apps/www/src/content/docs/components/collapsible/index.mdx
+++ b/apps/www/src/content/docs/components/collapsible/index.mdx
@@ -85,4 +85,5 @@ Every rendered part carries a stable `data-slot` attribute for [styling and test
- The Trigger uses `aria-expanded` to indicate the open/closed state of the panel.
- Supports keyboard interaction with Enter and Space to toggle the panel.
- The Panel is automatically associated with the Trigger via `aria-controls`.
+- A disabled Trigger has the native `disabled` attribute, so it is left out of the Tab order.
- Respects motion preferences: panel expand/collapse motion is enabled only when `prefers-reduced-motion: no-preference`
diff --git a/apps/www/src/content/docs/components/combobox/props.ts b/apps/www/src/content/docs/components/combobox/props.ts
index b5d486798..63203c163 100644
--- a/apps/www/src/content/docs/components/combobox/props.ts
+++ b/apps/www/src/content/docs/components/combobox/props.ts
@@ -7,15 +7,15 @@ export interface ComboboxRootProps {
/** The controlled value of the combobox.
* For single selection: string
- * For multiple selection: string[]
+ * For multiple selection: readonly string[]
*/
- value?: string | string[];
+ value?: string | readonly string[];
/** The default value of the combobox (uncontrolled).
* For single selection: string
- * For multiple selection: string[]
+ * For multiple selection: readonly string[]
*/
- defaultValue?: string | string[];
+ defaultValue?: string | readonly string[];
/** Callback fired when the value changes.
* For single selection: (value: string, eventDetails) => void
diff --git a/apps/www/src/content/docs/components/select/props.ts b/apps/www/src/content/docs/components/select/props.ts
index e89c80015..56d968554 100644
--- a/apps/www/src/content/docs/components/select/props.ts
+++ b/apps/www/src/content/docs/components/select/props.ts
@@ -1,9 +1,9 @@
export interface SelectRootProps {
/** The selected value. An array when `multiple` is set. */
- value?: string | string[];
+ value?: string | readonly string[];
/** The value selected on first render when `value` is not set. An array when `multiple` is set. */
- defaultValue?: string | string[];
+ defaultValue?: string | readonly string[];
/** Called when the selection changes. Receives an array when `multiple` is set. */
onValueChange?: (
diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md
index fa5ec7f72..35eef4e3f 100644
--- a/packages/react/CHANGELOG.md
+++ b/packages/react/CHANGELOG.md
@@ -2,6 +2,25 @@
## Unreleased
+### Base UI 1.9.0
+
+Apsara uses `@base-ui/react` 1.9.0 and `@base-ui/utils` 0.5.0.
+
+#### Changes
+
+- A disabled `Accordion` or `Collapsible` trigger has the native
+ `disabled` attribute. It is dimmed and left out of the Tab order.
+- A multiple `Combobox` or `Select` accepts a readonly array for `value`
+ and `defaultValue`. `onValueChange` still receives a mutable array.
+- In `Menu`, `ContextMenu` and `Menubar`, pressing the trigger of an open
+ submenu with touch or the keyboard closes it, and a submenu closes when
+ focus moves back to its trigger through its focus guard.
+- The `ScrollArea` viewport has no `role="presentation"`.
+- `Checkbox`, `Radio` and `Switch` take their accessible name from
+ `aria-label` when they also have an associated label.
+- A `Dialog`, `AlertDialog` or `Drawer` opened from code through a
+ handle is not linked to a trigger that did not open it.
+
### Icons — lucide replaces @radix-ui/react-icons (BREAKING)
Apsara names every icon with a stable key that does not name a library —
diff --git a/packages/react/components/combobox/__tests__/combobox.test.tsx b/packages/react/components/combobox/__tests__/combobox.test.tsx
index 6519bf2ff..e81ef0464 100644
--- a/packages/react/components/combobox/__tests__/combobox.test.tsx
+++ b/packages/react/components/combobox/__tests__/combobox.test.tsx
@@ -620,6 +620,13 @@ describe('Combobox chips', () => {
expect(screen.getByText('banana')).toBeInTheDocument();
});
+ it('accepts a readonly array as the value', () => {
+ const value = ['apple', 'banana'] as const;
+ render();
+ expect(screen.getByText('apple')).toBeInTheDocument();
+ expect(screen.getByText('banana')).toBeInTheDocument();
+ });
+
it('removes the last chip on Backspace in an empty input', async () => {
const user = userEvent.setup();
const handleValueChange = vi.fn();
diff --git a/packages/react/components/combobox/combobox-root.tsx b/packages/react/components/combobox/combobox-root.tsx
index 6ce0a56ce..c3a48d87f 100644
--- a/packages/react/components/combobox/combobox-root.tsx
+++ b/packages/react/components/combobox/combobox-root.tsx
@@ -64,8 +64,8 @@ export interface SingleComboboxProps
export interface MultipleComboboxProps
extends BaseComboboxRootProps {
multiple: true;
- value?: Value[];
- defaultValue?: Value[];
+ value?: readonly Value[];
+ defaultValue?: readonly Value[];
onValueChange?: (
value: Value[],
eventDetails: ComboboxPrimitive.Root.ChangeEventDetails
@@ -104,7 +104,7 @@ export const ComboboxRoot = ({
state: 'inputValue'
});
const [computedValue, setValueUnwrapped] = useControlled<
- Value | Value[] | null
+ Value | readonly Value[] | null
>({
controlled: providedValue,
default: defaultValue ?? null,
diff --git a/packages/react/components/select/__tests__/select.test.tsx b/packages/react/components/select/__tests__/select.test.tsx
index f5f6d1b00..51d7c59a9 100644
--- a/packages/react/components/select/__tests__/select.test.tsx
+++ b/packages/react/components/select/__tests__/select.test.tsx
@@ -343,6 +343,28 @@ describe('Select', () => {
await flushMicrotasks();
expect(handleValueChange).toHaveBeenCalledTimes(1);
});
+
+ it('accepts a readonly array as the value', async () => {
+ const value = ['apple', 'banana'] as const;
+ const user = userEvent.setup();
+ render(
+
+ );
+ await openSelect(user);
+
+ expect(
+ within(screen.getAllByRole('option')[0]).getByRole('checkbox')
+ ).toHaveAttribute('aria-checked', 'true');
+ });
});
describe('Keyboard Navigation', () => {
diff --git a/packages/react/components/select/select-root.tsx b/packages/react/components/select/select-root.tsx
index a5a2d3056..a1a23751e 100644
--- a/packages/react/components/select/select-root.tsx
+++ b/packages/react/components/select/select-root.tsx
@@ -22,7 +22,7 @@ interface CommonProps {
}
interface SelectContextValue extends CommonProps {
- value?: string | string[];
+ value?: string | readonly string[];
inputValue?: string;
registerItem: (item: ItemType) => void;
unregisterItem: (value: string) => void;
@@ -94,12 +94,12 @@ export interface SingleSelectProps extends BaseSelectProps {
export interface MultipleSelectProps extends BaseSelectProps {
multiple: true;
- value?: string[];
+ value?: readonly string[];
onValueChange?: (
value: string[],
eventDetails: SelectChangeEventDetails
) => void;
- defaultValue?: string[];
+ defaultValue?: readonly string[];
}
export type SelectRootProps = SingleSelectProps | MultipleSelectProps;
@@ -130,7 +130,7 @@ export const SelectRoot = (props: SelectRootProps) => {
const resolvedRequired = required ?? fieldContext?.required;
const [computedValue, setValueUnwrapped] = useControlled<
- string | string[] | undefined
+ string | readonly string[] | undefined
>({
controlled: providedValue,
default: defaultValue,