Directory structure: └── radix-ui-primitives/ ├── README.md ├── CODE_OF_CONDUCT.md ├── context7.json ├── cypress.config.ts ├── eslint.config.mjs ├── LICENSE ├── package.json ├── philosophy.md ├── pnpm-workspace.yaml ├── release-process.md ├── vitest.config.mts ├── .editorconfig ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── apps/ │ ├── ssr-testing/ │ │ ├── README.md │ │ ├── next-env.d.ts │ │ ├── next.config.js │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── app/ │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── accessible-icon/ │ │ │ └── page.tsx │ │ ├── accordion/ │ │ │ └── page.tsx │ │ ├── alert-dialog/ │ │ │ └── page.tsx │ │ ├── avatar/ │ │ │ └── page.tsx │ │ ├── checkbox/ │ │ │ └── page.tsx │ │ ├── collapsible/ │ │ │ └── page.tsx │ │ ├── context-menu/ │ │ │ └── page.tsx │ │ ├── dialog/ │ │ │ └── page.tsx │ │ ├── dropdown-menu/ │ │ │ └── page.tsx │ │ ├── form/ │ │ │ └── page.tsx │ │ ├── hover-card/ │ │ │ └── page.tsx │ │ ├── label/ │ │ │ └── page.tsx │ │ ├── menubar/ │ │ │ └── page.tsx │ │ ├── navigation-menu/ │ │ │ └── page.tsx │ │ ├── one-time-password-field/ │ │ │ └── page.tsx │ │ ├── password-toggle-field/ │ │ │ └── page.tsx │ │ ├── popover/ │ │ │ └── page.tsx │ │ ├── portal/ │ │ │ ├── conditional-portal.tsx │ │ │ ├── custom-portal-container.tsx │ │ │ └── page.tsx │ │ ├── progress/ │ │ │ └── page.tsx │ │ ├── radio-group/ │ │ │ └── page.tsx │ │ ├── roving-focus-group/ │ │ │ ├── page.tsx │ │ │ └── roving-focus.client.tsx │ │ ├── scroll-area/ │ │ │ └── page.tsx │ │ ├── select/ │ │ │ └── page.tsx │ │ ├── separator/ │ │ │ └── page.tsx │ │ ├── slider/ │ │ │ └── page.tsx │ │ ├── slot/ │ │ │ └── page.tsx │ │ ├── switch/ │ │ │ └── page.tsx │ │ ├── tabs/ │ │ │ └── page.tsx │ │ ├── toast/ │ │ │ └── page.tsx │ │ ├── toggle-group/ │ │ │ └── page.tsx │ │ ├── toolbar/ │ │ │ └── page.tsx │ │ ├── tooltip/ │ │ │ └── page.tsx │ │ └── visually-hidden/ │ │ └── page.tsx │ └── storybook/ │ ├── eslint.config.js │ ├── index.d.ts │ ├── package.json │ ├── tsconfig.json │ ├── stories/ │ │ ├── accessible-icon.stories.tsx │ │ ├── accordion.stories.module.css │ │ ├── accordion.stories.tsx │ │ ├── alert-dialog.stories.module.css │ │ ├── alert-dialog.stories.tsx │ │ ├── arrow.stories.tsx │ │ ├── aspect-ratio.stories.module.css │ │ ├── aspect-ratio.stories.tsx │ │ ├── avatar.stories.module.css │ │ ├── avatar.stories.tsx │ │ ├── checkbox.stories.module.css │ │ ├── checkbox.stories.tsx │ │ ├── collapsible.stories.module.css │ │ ├── collapsible.stories.tsx │ │ ├── collection.stories.tsx │ │ ├── context-menu.stories.module.css │ │ ├── context-menu.stories.tsx │ │ ├── dialog.stories.module.css │ │ ├── dialog.stories.tsx │ │ ├── dismissable-layer.stories.tsx │ │ ├── dropdown-menu.stories.module.css │ │ ├── focus-scope.stories.tsx │ │ ├── form.stories.module.css │ │ ├── form.stories.tsx │ │ ├── hover-card.stories.module.css │ │ ├── hover-card.stories.tsx │ │ ├── label.stories.module.css │ │ ├── label.stories.tsx │ │ ├── menu.stories.module.css │ │ ├── menu.stories.tsx │ │ ├── menubar.stories.module.css │ │ ├── menubar.stories.tsx │ │ ├── navigation-menu.stories.module.css │ │ ├── navigation-menu.stories.tsx │ │ ├── one-time-password-field.stories.module.css │ │ ├── one-time-password-field.stories.tsx │ │ ├── password-toggle-field.stories.module.css │ │ ├── password-toggle-field.stories.tsx │ │ ├── popover.stories.module.css │ │ ├── popover.stories.tsx │ │ ├── popper.stories.module.css │ │ ├── popper.stories.tsx │ │ ├── portal.stories.tsx │ │ ├── presence.stories.module.css │ │ ├── presence.stories.tsx │ │ ├── progress.stories.module.css │ │ ├── progress.stories.tsx │ │ ├── radio-group.stories.module.css │ │ ├── radio-group.stories.tsx │ │ ├── roving-focus-group.stories.tsx │ │ ├── scroll-area.stories.module.css │ │ ├── scroll-area.stories.tsx │ │ ├── select.stories.module.css │ │ ├── select.stories.tsx │ │ ├── separator.stories.module.css │ │ ├── separator.stories.tsx │ │ ├── slider.stories.module.css │ │ ├── slider.stories.tsx │ │ ├── slot.stories.tsx │ │ ├── switch.stories.module.css │ │ ├── switch.stories.tsx │ │ ├── tabs.stories.module.css │ │ ├── tabs.stories.tsx │ │ ├── toast.stories.module.css │ │ ├── toast.stories.tsx │ │ ├── toggle-group.stories.module.css │ │ ├── toggle-group.stories.tsx │ │ ├── toggle.stories.module.css │ │ ├── toggle.stories.tsx │ │ ├── toolbar.stories.module.css │ │ ├── toolbar.stories.tsx │ │ ├── tooltip.stories.module.css │ │ ├── tooltip.stories.tsx │ │ └── visually-hidden.stories.tsx │ └── .storybook/ │ ├── main.ts │ ├── manager-head.html │ ├── manager.ts │ ├── preview.css │ └── preview.ts ├── cypress/ │ ├── tsconfig.json │ ├── e2e/ │ │ ├── ContextMenu.cy.ts │ │ ├── Dialog.cy.ts │ │ ├── DropdownMenu.cy.ts │ │ ├── Form.cy.ts │ │ ├── Menubar.cy.ts │ │ ├── Select.cy.ts │ │ └── Toast.cy.ts │ └── support/ │ ├── commands.js │ ├── e2e.js │ └── index.d.ts ├── internal/ │ ├── builder/ │ │ ├── builder.js │ │ ├── eslint.config.js │ │ ├── package.json │ │ ├── radix-build.js │ │ └── tsconfig.json │ ├── eslint-config/ │ │ ├── eslint.config.js │ │ ├── index.js │ │ ├── package.json │ │ ├── react-package.js │ │ ├── tsconfig.json │ │ └── vite.js │ ├── test-data/ │ │ ├── eslint.config.js │ │ ├── foods.ts │ │ ├── package.json │ │ └── tsconfig.json │ └── typescript-config/ │ ├── base.json │ ├── nextjs.json │ ├── package.json │ ├── react-library.json │ ├── vite-app.json │ ├── vite-node.json │ └── react-library/ │ └── index.d.ts ├── packages/ │ ├── core/ │ │ ├── number/ │ │ │ ├── README.md │ │ │ ├── eslint.config.mjs │ │ │ ├── package.json │ │ │ ├── tsconfig.json │ │ │ └── src/ │ │ │ ├── index.ts │ │ │ └── number.ts │ │ ├── primitive/ │ │ │ ├── README.md │ │ │ ├── CHANGELOG.md │ │ │ ├── eslint.config.mjs │ │ │ ├── package.json │ │ │ ├── tsconfig.json │ │ │ └── src/ │ │ │ ├── index.ts │ │ │ ├── primitive.tsx │ │ │ └── types.ts │ │ └── rect/ │ │ ├── README.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── observe-element-rect.ts │ └── react/ │ ├── accessible-icon/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── accesible-icon.test.tsx │ │ ├── accessible-icon.tsx │ │ └── index.ts │ ├── accordion/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── accordion.test.tsx │ │ ├── accordion.tsx │ │ └── index.ts │ ├── alert-dialog/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── alert-dialog.test.tsx │ │ ├── alert-dialog.tsx │ │ └── index.ts │ ├── announce/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── announce.tsx │ │ └── index.ts │ ├── arrow/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── arrow.test.tsx │ │ ├── arrow.tsx │ │ └── index.ts │ ├── aspect-ratio/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── aspect-ratio.test.tsx │ │ ├── aspect-ratio.tsx │ │ └── index.ts │ ├── avatar/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── avatar.test.tsx │ │ ├── avatar.tsx │ │ └── index.ts │ ├── checkbox/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── checkbox.test.tsx │ │ ├── checkbox.tsx │ │ └── index.ts │ ├── collapsible/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── collapsible.test.tsx │ │ ├── collapsible.tsx │ │ └── index.ts │ ├── collection/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── collection-legacy.tsx │ │ ├── collection.tsx │ │ ├── index.ts │ │ ├── ordered-dictionary.test.ts │ │ └── ordered-dictionary.ts │ ├── compose-refs/ │ │ ├── README.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── compose-refs.tsx │ │ └── index.ts │ ├── context/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── create-context.tsx │ │ └── index.ts │ ├── context-menu/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── context-menu.tsx │ │ └── index.ts │ ├── dialog/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── dialog.test.tsx │ │ ├── dialog.tsx │ │ └── index.ts │ ├── direction/ │ │ ├── README.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── direction.tsx │ │ └── index.ts │ ├── dismissable-layer/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── dismissable-layer.tsx │ │ └── index.ts │ ├── dropdown-menu/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── dropdown-menu.tsx │ │ └── index.ts │ ├── focus-guards/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── focus-guards.tsx │ │ └── index.ts │ ├── focus-scope/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── focus-scope.test.tsx │ │ ├── focus-scope.tsx │ │ └── index.ts │ ├── form/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── form.tsx │ │ └── index.ts │ ├── hover-card/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── hover-card.tsx │ │ └── index.ts │ ├── id/ │ │ ├── README.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── id.tsx │ │ └── index.ts │ ├── label/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── label.tsx │ ├── menu/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ └── index.ts │ ├── menubar/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── menubar.tsx │ ├── navigation-menu/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── navigation-menu.tsx │ ├── one-time-password-field/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ ├── one-time-password-field.test.tsx │ │ └── one-time-password-field.tsx │ ├── password-toggle-field/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ ├── password-toggle-field.test.tsx │ │ └── password-toggle-field.tsx │ ├── popover/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── popover.tsx │ ├── popper/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── popper.tsx │ ├── portal/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── portal.tsx │ ├── presence/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ ├── presence.tsx │ │ └── use-state-machine.tsx │ ├── primitive/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── primitive.tsx │ ├── progress/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── progress.tsx │ ├── radio-group/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ ├── radio-group.tsx │ │ └── radio.tsx │ ├── radix-ui/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── internal.ts │ ├── roving-focus/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── roving-focus-group.tsx │ ├── scroll-area/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ ├── scroll-area.tsx │ │ └── use-state-machine.ts │ ├── select/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ └── index.ts │ ├── separator/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── separator.tsx │ ├── slider/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── slider.tsx │ ├── slot/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ ├── slot.test.tsx │ │ ├── slot.tsx │ │ └── __snapshots__/ │ │ └── slot.test.tsx.snap │ ├── switch/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ ├── switch.test.tsx │ │ └── switch.tsx │ ├── tabs/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── tabs.tsx │ ├── toast/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── toast.tsx │ ├── toggle/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ ├── toggle.test.tsx │ │ └── toggle.tsx │ ├── toggle-group/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ ├── toggle-group.test.tsx │ │ └── toggle-group.tsx │ ├── toolbar/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ ├── toolbar.test.tsx │ │ └── toolbar.tsx │ ├── tooltip/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ ├── tooltip.test.tsx │ │ └── tooltip.tsx │ ├── use-callback-ref/ │ │ ├── README.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── use-callback-ref.tsx │ ├── use-controllable-state/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ ├── use-controllable-state-reducer.tsx │ │ ├── use-controllable-state.test.tsx │ │ └── use-controllable-state.tsx │ ├── use-effect-event/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── use-effect-event.tsx │ ├── use-escape-keydown/ │ │ ├── README.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── use-escape-keydown.tsx │ ├── use-is-hydrated/ │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── use-is-hydrated.tsx │ ├── use-layout-effect/ │ │ ├── README.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── use-layout-effect.tsx │ ├── use-previous/ │ │ ├── README.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── use-previous.tsx │ ├── use-rect/ │ │ ├── README.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── use-rect.tsx │ ├── use-size/ │ │ ├── README.md │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts │ │ └── use-size.tsx │ └── visually-hidden/ │ ├── README.md │ ├── CHANGELOG.md │ ├── eslint.config.mjs │ ├── package.json │ ├── tsconfig.json │ └── src/ │ ├── index.ts │ └── visually-hidden.tsx ├── patches/ │ └── @changesets__apply-release-plan.patch ├── scripts/ │ └── setup-tests.ts ├── types/ │ ├── global.d.ts │ └── index.d.ts ├── .changeset/ │ ├── README.md │ ├── changelog.cjs │ ├── config.json │ ├── silent-turkeys-fly.md │ └── ten-pumas-agree.md └── .github/ ├── CODEOWNERS ├── CONTRIBUTING.md ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE/ │ ├── Bug_report.md │ ├── Documentation.md │ ├── Feature_request.md │ └── Question.md └── workflows/ ├── build.yml ├── chromatic.yml ├── publish-snapshot.yml └── publish-stable.yml ================================================ FILE: README.md ================================================ [![Radix Primitives Logo](primitives.png)](https://radix-ui.com/primitives) # Radix Primitives **An open-source UI component library for building high-quality, accessible design systems and web apps.** Radix Primitives is a low-level UI component library with a focus on accessibility, customization and developer experience. You can use these components either as the base layer of your design system, or adopt them incrementally. --- ## Installation First, install pnpm if you haven't already. Open your terminal and run: ```bash npm install -g pnpm ``` Then, install the dependencies: ```bash pnpm install ``` ## Documentation For full documentation, visit [radix-ui.com/primitives/docs](https://www.radix-ui.com/primitives/docs). ## Releases For changelog, visit [radix-ui.com/primitives/docs/overview/releases](https://www.radix-ui.com/primitives/docs/overview/releases). ## Contributing Please follow our [contributing guidelines](./.github/CONTRIBUTING.md). --- ## Community - [Discord](https://discord.com/invite/7Xb99uG) - To get involved with the Radix community, ask questions and share tips. - [Twitter](https://twitter.com/radix_ui) - To receive updates, announcements, blog posts, and general Radix tips. ## Thanks Chromatic Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions. --- ## License Licensed under the MIT License, Copyright © 2022-present [WorkOS](https://workos.com). ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at colm@workos.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: context7.json ================================================ { "url": "https://context7.com/radix-ui/primitives", "public_key": "pk_q7NnKuFFXMWA7WnmjMHQU" } ================================================ FILE: cypress.config.ts ================================================ import { defineConfig } from 'cypress'; export default defineConfig({ viewportWidth: 1024, viewportHeight: 768, fixturesFolder: false, defaultCommandTimeout: 20_000, e2e: { // TODO There is a bug (I think) in Cypress that results in the page // navigating to `about:blank` after certain tests run. Unclear why at this // stage. This is a hack that seems to prevent it, but unclear why. Debug // and remove this config option. // Possibly related: https://github.com/cypress-io/cypress/issues/28527 testIsolation: false, setupNodeEvents(_on, _config) {}, baseUrl: 'http://localhost:9009', }, }); ================================================ FILE: eslint.config.mjs ================================================ // @ts-check import * as react from '@chance/eslint/react'; import * as js from '@chance/eslint'; import * as typescript from '@chance/eslint/typescript'; import { globals } from '@chance/eslint/globals'; import pluginCypress from 'eslint-plugin-cypress/flat'; /** @type {import("eslint").Linter.Config[]} */ export default [ { ...js.getConfig({ ...globals.node, ...globals.browser }) }, typescript.config, { ...react.config, rules: { ...react.rules, 'react/jsx-pascal-case': ['warn', { allowNamespace: true }], // TODO: enable this and fix all the errors 'react/display-name': 'off', 'prefer-const': ['warn', { destructuring: 'all' }], 'jsx-a11y/label-has-associated-control': [ 'warn', { controlComponents: ['Checkbox'], depth: 3, }, ], }, }, { ...pluginCypress.configs.recommended, files: ['cypress/**/*.{ts,js}'] }, { ignores: ['dist/**', '.next/**'], rules: { 'prefer-const': ['warn', { destructuring: 'all' }], }, }, ]; ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2022 WorkOS Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: package.json ================================================ { "private": true, "name": "primitives", "version": "0.1.0", "license": "MIT", "scripts": { "lint": "pnpm -r run lint", "types:check": "pnpm -r --parallel run typecheck", "test": "vitest", "test:ci": "vitest run && pnpm cypress:ci", "storybook": "BROWSER=none pnpm --filter=@repo/storybook run dev -p 9009", "storybook:build": "BROWSER=none pnpm --filter=@repo/storybook run build", "storybook:serve": "pnpm --filter=@repo/storybook run serve", "cypress:ci": "start-server-and-test 'pnpm dev' http://localhost:9009 'pnpm cypress:install && pnpm cypress:run'", "cypress:install": "cypress install", "cypress:run": "cypress run", "cypress:dev": "cypress open", "dev": "pnpm run storybook", "dev:ssr": "pnpm --filter=@repo/ssr-testing dev", "build-storybook": "pnpm --filter=@repo/storybook run build", "build": "pnpm -r --parallel --filter \"./packages/**/*\" run build", "clean": "pnpm -r run clean", "reset": "rm -rf node_modules && pnpm -r run reset", "bump:stable": "changeset version", "bump:next": "changeset version --snapshot", "bump:check": "changeset status --since=main", "release:stable": "pnpm publish -r --access public", "release:stable:ci": "pnpm publish -r --access public --publish-branch stable", "release:next": "pnpm publish -r --access public --tag next", "format": "prettier --write .", "format:staged": "pretty-quick --staged" }, "devDependencies": { "@chance/eslint": "^1.1.0", "@changesets/cli": "^2.29.7", "@radix-ui/colors": "^3.0.0", "@testing-library/cypress": "^10.1.0", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", "@types/fs-extra": "^11", "@types/node": "^22", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", "@types/react-test-renderer": "^19.1.0", "axe-core": "^4.11.0", "cypress": "^15.5.0", "cypress-axe": "1.7.0", "cypress-real-events": "^1.15.0", "eslint": "^9.38.0", "eslint-plugin-cypress": "^5.2.0", "execa": "^9.6.0", "fs-extra": "^11.3.2", "glob": "^10.2.2", "jsdom": "^26.1.0", "prettier": "^3.6.2", "pretty-quick": "^4.2.2", "react": "^19.2.0", "react-dom": "^19.2.0", "react-test-renderer": "^19.2.0", "react-is": "^19.2.0", "react-remove-scroll": "^2.6.3", "replace-in-files": "^3.0.0", "start-server-and-test": "^2.1.2", "typescript": "^5.9.3", "vite": "^7.1.12", "vitest": "^4.0.4", "vitest-axe": "^1.0.0-pre.5" }, "engines": { "node": ">=18" }, "packageManager": "pnpm@10.2.0", "pnpm": { "patchedDependencies": { "@changesets/apply-release-plan": "patches/@changesets__apply-release-plan.patch" } } } ================================================ FILE: philosophy.md ================================================ # Primitives: Philosophy and Guiding Principles ## Vision Most of us share similar definitions for common UI patterns like accordion, checkbox, combobox, dialog, dropdown, select, slider, and tooltip. These UI patterns are [documented by WAI-ARIA](https://www.w3.org/TR/wai-aria-practices/#aria_ex) and generally understood by the community. However, the implementations provided to us by the web platform are inadequate. They're either non-existent, lacking in functionality, or cannot be customised sufficiently. As a result, developers are forced to build custom components—an incredibly difficult task. As a result, most components on the web are inaccessible, non-performant, and lacking important features. Our goal is to create a well-funded open-source component library that the community can use to build accessible design systems. ## Principles at a glance 1. Accessible 2. Functional 3. Interoperable 4. Composable 5. Customizable --- ## Principles ### Accessible - Components adhere to WAI-ARIA guidelines and are tested regularly in a wide selection of modern browsers and assistive technologies. - Where WAI-ARIA guidelines do not cover a particular use case, prior research is done to determine the patterns and behaviors we adopt when designing a new component. We look to similar, well-tested native solutions to capture nuances that WAI-ARIA guidelines may overlook. - Developers should know about accessibility but shouldn't have to spend too much time implementing accessible patterns. - Most behavior and markup related to accessibility should be abstracted, and bits that can't should be simplified where possible. - Individual components will be tested to ensure maximum accessibility, but where app context is required the component library should provide useful guidance and supporting materials where possible to build fully accessible applications. - Try to name things as closely to `aria` and `html` as possible where applicable; wherever we require developers to engage with accessibility directly, our platform should be a learning opportunity and a bridge for better understanding the underlying problems we solve. - Components are thoroughly tested on a variety of devices and assistive technology, including all major screen reader vendors (VoiceOver, JAWS, NVDA); components respond and adapt effectively to input and appearance distinctions between platforms. ### Functional - Components are feature-rich, with support for keyboard interaction, collision detection, focus trapping, dynamic resizing, scroll locking, native fallbacks, and more. ### Composable - Components are designed with an open API that provides consumers with direct access to the underlying DOM node that is rendered to the page. - We achieve this API with a 1-to-1 strategy, where a single component only renders a single DOM element (if a DOM node is rendered at all). - Some abstractions may require slight deviation from this pattern, in which case the rationale should be clearly explained in supporting documentation. - This API also empowers us to forward user-provided DOM refs to the correct underlying DOM node without doing anything too clever, meaning refs function exactly as the consumer would expect. - Just as DOM nodes are composable, so are DOM event handlers; consumers should be able to pass their own event handlers directly to a component and stop internal handlers from firing. ### Customizable - Components are built to be themed; no need to override opinionated styles, as primitives ship with zero presentational styles applied by default. - Components ship with CSS-in-JS style objects that provide a minimal set styles needed to easily reset user agent styles and provide a clean slate for consumers to build upon. - Our components can be composed or styled the same way underlying JSX components are composed or styled, with limitations only introduced to prevent UX/accessibility dark patterns where needed. - Consumers can choose whether or not to apply these styles in their app, as well as the styling tool; we do not enforce a particular methodology or library. ## Other considerations ### Internationalization - Components support international string formatting and make behavioral adjustments for right-to-left languages ### Stateful components can be controlled or uncontrolled - Similar to form field JSX elements in React, all components with internal state can either be uncontrolled (internally managed) or controlled (managed by the consumer) ### Components exist in a finite number of predefined states - State in this context refers to a component's state representable by a finite state machine; not to be confused with arbitrary stateful data as typically referenced in React libraries - States are predetermined during the component design phase and expressed as strings in component code, making state transitions more explicit, deterministic, and clearer to follow - Use the `data-state` attribute to expose a component's state directly to its DOM element - When tempted to use a boolean to track a piece of stateful data, consider enumerated strings instead ### Developer experience - Component APIs should be relatively intuitive and as declarative as possible - Provide in-code documentation for complex/unclear abstractions for easier source debugging - Anticipate errors and provide thorough console warnings with links back to documentation ### Balancing tradeoffs between design goals - Composition is preferred over configuration - Code clarity is preferred over bundle terseness except in extreme cases - Smart abstractions preferred over over-exposing internal state ### Documentation TODO ### Misc - Not concerned with design system components like `Box`, `Chip` or `Badge` that provide visual language consistency but provide no underlying semantic meaning or abstracted behavior - Keep file structure flat so logic is easier to follow; avoid early abstractions - Don't repeat yourself _too much_ but don't be afraid to repeat yourself if an implementation detail hasn't been thoroughly vetted ================================================ FILE: pnpm-workspace.yaml ================================================ packages: - 'apps/*' - 'internal/*' - 'packages/*/*' ================================================ FILE: release-process.md ================================================ # Release process ## Overview While our release schedule is flexible, our general strategy is to release several larger improvements inside each stable release. In order to provide faster access to fixes and enhancements between main releases we provide release candidates which are published on every merge into `main`. While the versioning and publishing of our primitives is mostly automated via scripts, updates to our [documentation website](https://radix-ui.com/primitives/docs/overview/introduction) is currently a manual process. We are working to improve this but for now this outline should help contributors with the process. ## Release strategy We track versions during the pull request process. As features are added, modified or improved it's important to keep track of these via versioning. ### Tracking version changes Run `pnpm changeset` to mark the appropriate type of change for those packages. This is later consumed when publishing new versions. Be sure to check-in these files along with your code changes. ### Publishing a stable release 1. Checkout the `stable` branch and pull the latest changes from `main` 2. Push the changes to `stable`. This will trigger the Changeset action, which will create a new release PR. 3. Review the release PR, ensure all tests pass and make any necessary changes. 4. Merge the PR. This will trigger the Changeset action to publish the new version to npm. ### Release candidates Release candidates are automatically published when new changes are merged into `main`. ## Updating documentation Our documentation is in a [separate repository](https://github.com/radix-ui/website) and updating it is a three step process: 1. Write and update the [change log](https://github.com/radix-ui/website/blob/main/data/primitives/docs/overview/releases.mdx) 2. Bump package version/s and create / update the pages for each version change 3. Perform documentation updates and remove live demos from previous versions Steps 2 and 3 are typically raised as separate pull requests to make changes easier to review. ### Creating new version pages This is as simple as duplicating the latest page and updating the version number to match the release. Some things to keep in mind: - We only provide live demos for the latest version of a package so you must remember to disable/remove the previous live demo (this avoids breaking changes affecting old versions in our docs) - If the incoming version is a patch which doesn't require a docs update then you can simply change the page name to match the new version rather than duplicating the same content ================================================ FILE: vitest.config.mts ================================================ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { setupFiles: ['./scripts/setup-tests.ts'], environment: 'jsdom', include: ['**/*.test.?(c|m)[jt]s?(x)'], retry: 1, }, }); ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf indent_size = 2 indent_style = space insert_final_newline = true max_line_length = 80 tab_width = 2 trim_trailing_whitespace = true [Makefile] indent_style = tab tab_width = 4 [{*.md,*.mdx}] trim_trailing_whitespace = false ================================================ FILE: .nvmrc ================================================ 22 ================================================ FILE: .prettierignore ================================================ .next node_modules .yarn dist storybook-static .pnp .pnp.js coverage pnpm-lock.yaml package-lock.json yarn.lock *.log* .DS_Store *.pem ================================================ FILE: .prettierrc ================================================ { "printWidth": 100, "singleQuote": true } ================================================ FILE: apps/ssr-testing/README.md ================================================ # `@repo/ssr-testing` This is a testing playground for SSR support in our primitives using a [Next.js](https://nextjs.org/) project. ## Getting Started From the root of the repo, run the development server: ```sh pnpm dev:ssr ``` ================================================ FILE: apps/ssr-testing/next-env.d.ts ================================================ /// /// // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. ================================================ FILE: apps/ssr-testing/next.config.js ================================================ module.exports = { experimental: { externalDir: true, }, }; ================================================ FILE: apps/ssr-testing/package.json ================================================ { "name": "@repo/ssr-testing", "version": "0.0.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start" }, "dependencies": { "next": "^15.3.1", "radix-ui": "workspace:*", "react": "^19.2.0", "react-dom": "^19.2.0" }, "devDependencies": { "@repo/eslint-config": "workspace:*", "@repo/typescript-config": "workspace:*", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", "eslint": "^9.38.0", "typescript": "^5.9.3" } } ================================================ FILE: apps/ssr-testing/tsconfig.json ================================================ { "extends": "@repo/typescript-config/nextjs.json", "compilerOptions": { "incremental": true }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] } ================================================ FILE: apps/ssr-testing/app/layout.tsx ================================================ import * as React from 'react'; import type { Metadata } from 'next'; import Link from 'next/link'; export default function Layout({ children }: { children: React.ReactNode }) { return (

SSR / RSC testing

AccessibleIcon Accordion AlertDialog Avatar Checkbox Collapsible ContextMenu Dialog DropdownMenu Form HoverCard Label Menubar NavigationMenu OneTimePasswordField PasswordToggleField Popover Portal Progress RadioGroup RovingFocusGroup ScrollArea Select Separator Slider Slot Switch Tabs Toast ToggleGroup Toolbar Tooltip VisuallyHidden
{children}
); } export const metadata: Metadata = { title: 'SSR testing', }; ================================================ FILE: apps/ssr-testing/app/page.tsx ================================================ export default function Page() { return null; } ================================================ FILE: apps/ssr-testing/app/accessible-icon/page.tsx ================================================ import * as React from 'react'; import { AccessibleIcon } from 'radix-ui'; export default function Page() { return ( ); } ================================================ FILE: apps/ssr-testing/app/accordion/page.tsx ================================================ import * as React from 'react'; import { Accordion } from 'radix-ui'; export default function Page() { return ( One Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. Two Cursus sed mattis commodo fermentum conubia ipsum pulvinar sagittis, diam eget bibendum porta nascetur ac dictum, leo tellus dis integer platea ultrices mi. Three (disabled) Sociis hac sapien turpis conubia sagittis justo dui, inceptos penatibus feugiat himenaeos euismod magna, nec tempor pulvinar eu etiam mattis. Four Odio placerat quisque sapien sagittis non sociis ligula penatibus dignissim vitae, enim vulputate nullam semper potenti etiam volutpat libero. ); } ================================================ FILE: apps/ssr-testing/app/alert-dialog/page.tsx ================================================ import * as React from 'react'; import { AlertDialog } from 'radix-ui'; export default function Page() { return ( delete everything Are you sure? This will do a very dangerous thing. Thar be dragons! yolo, do it maybe not ); } ================================================ FILE: apps/ssr-testing/app/avatar/page.tsx ================================================ import * as React from 'react'; import { Avatar } from 'radix-ui'; export default function Page() { return ( A ); } ================================================ FILE: apps/ssr-testing/app/checkbox/page.tsx ================================================ import * as React from 'react'; import { Checkbox } from 'radix-ui'; export default function Page() { return ( [ ] ); } ================================================ FILE: apps/ssr-testing/app/collapsible/page.tsx ================================================ import * as React from 'react'; import { Collapsible } from 'radix-ui'; export default function Page() { return ( Trigger Content ); } ================================================ FILE: apps/ssr-testing/app/context-menu/page.tsx ================================================ import * as React from 'react'; import { ContextMenu } from 'radix-ui'; export default function Page() { return ( Right click here Undo Redo Cut Copy Paste ); } ================================================ FILE: apps/ssr-testing/app/dialog/page.tsx ================================================ import * as React from 'react'; import { Dialog } from 'radix-ui'; export default function Page() { return ( open Title Description close ); } ================================================ FILE: apps/ssr-testing/app/dropdown-menu/page.tsx ================================================ import * as React from 'react'; import { DropdownMenu } from 'radix-ui'; export default function Page() { return ( Open Undo Redo Cut Copy Paste ); } ================================================ FILE: apps/ssr-testing/app/form/page.tsx ================================================ import * as React from 'react'; import { Form } from 'radix-ui'; export default function Page() { return ( Email Value is missing Email is invalid ); } ================================================ FILE: apps/ssr-testing/app/hover-card/page.tsx ================================================ import * as React from 'react'; import { HoverCard } from 'radix-ui'; export default function Page() { return ( Hover me Nicely done! ); } ================================================ FILE: apps/ssr-testing/app/label/page.tsx ================================================ import * as React from 'react'; import { Label } from 'radix-ui'; export default function Page() { return Label; } ================================================ FILE: apps/ssr-testing/app/menubar/page.tsx ================================================ import * as React from 'react'; import { Menubar } from 'radix-ui'; export default function Page() { return ( Open Menu Item 1 Item 2 Item 3 ); } ================================================ FILE: apps/ssr-testing/app/navigation-menu/page.tsx ================================================ import * as React from 'react'; import { NavigationMenu } from 'radix-ui'; export default function Page() { return ( Nav Menu Item 1 Link Nav Menu Item 2 ); } ================================================ FILE: apps/ssr-testing/app/one-time-password-field/page.tsx ================================================ import * as React from 'react'; import { unstable_OneTimePasswordField as OneTimePasswordField } from 'radix-ui'; export default function Page() { return (

With indices

); } ================================================ FILE: apps/ssr-testing/app/password-toggle-field/page.tsx ================================================ import * as React from 'react'; import { unstable_PasswordToggleField as PasswordToggleField } from 'radix-ui'; export default function Page() { return (
} hidden={} />
); } function EyeClosedIcon() { return ( ); } function EyeOpenIcon() { return ( ); } ================================================ FILE: apps/ssr-testing/app/popover/page.tsx ================================================ import * as React from 'react'; import { Popover } from 'radix-ui'; export default function Page() { return ( open close ); } ================================================ FILE: apps/ssr-testing/app/portal/conditional-portal.tsx ================================================ 'use client'; import * as React from 'react'; import { Portal } from 'radix-ui'; export const ConditionalPortal = () => { const [container, setContainer] = React.useState(null); const [open, setOpen] = React.useState(false); return (
{open && ( This content is rendered in a custom container )}
); }; ================================================ FILE: apps/ssr-testing/app/portal/custom-portal-container.tsx ================================================ 'use client'; import * as React from 'react'; import { Portal } from 'radix-ui'; export const CustomPortalContainer = () => { const [container, setContainer] = React.useState(null); return (
This content is rendered in a custom container
); }; ================================================ FILE: apps/ssr-testing/app/portal/page.tsx ================================================ import * as React from 'react'; import { Portal } from 'radix-ui'; import { CustomPortalContainer } from './custom-portal-container'; import { ConditionalPortal } from './conditional-portal'; export default function Page() { return (

This content is rendered in the main DOM tree

Lorem ipsum dolor sit amet consectetur adipisicing elit. Quos porro, est ex quia itaque facere fugit necessitatibus aut enim. Nisi rerum quae, repellat in perspiciatis explicabo laboriosam necessitatibus eius pariatur.

This content is rendered in a portal (another DOM tree)

Because of the portal, it can appear in a different DOM tree from the main one (by default a new element inside the body), even though it is part of the same React tree.



); } ================================================ FILE: apps/ssr-testing/app/progress/page.tsx ================================================ import * as React from 'react'; import { Progress } from 'radix-ui'; export default function Page() { return ( Progress ); } ================================================ FILE: apps/ssr-testing/app/radio-group/page.tsx ================================================ import * as React from 'react'; import { Label, RadioGroup } from 'radix-ui'; export default function Page() { return ( Favourite pet [ X ] Cat
[ X ] Dog
[ X ] Rabbit
); } ================================================ FILE: apps/ssr-testing/app/roving-focus-group/page.tsx ================================================ import * as React from 'react'; import { RovingFocusProvider, RovingFocusToggle, ButtonGroup, Button } from './roving-focus.client'; export default function Page() { return ( <>

Basic

no orientation (both) + no looping

no orientation (both) + looping

horizontal orientation + no looping

horizontal orientation + looping

vertical orientation + no looping

vertical orientation + looping

Nested

); } ================================================ FILE: apps/ssr-testing/app/roving-focus-group/roving-focus.client.tsx ================================================ 'use client'; import * as React from 'react'; import { composeEventHandlers, RovingFocus } from 'radix-ui/internal'; type Direction = 'ltr' | 'rtl'; const RovingFocusContext = React.createContext<{ dir: 'ltr' | 'rtl'; setDir: React.Dispatch>; }>({ dir: 'ltr', setDir: () => void 0, }); RovingFocusContext.displayName = 'RovingFocusContext'; export function RovingFocusProvider({ children }: { children: React.ReactNode }) { const [dir, setDir] = React.useState('ltr'); return (
{children}
); } export function RovingFocusToggle() { const { dir, setDir } = React.use(RovingFocusContext); return ( ); } const ButtonGroupContext = React.createContext<{ value?: string; setValue: React.Dispatch>; }>({} as any); type ButtonGroupProps = Omit, 'defaultValue'> & RovingFocus.RovingFocusGroupProps & { defaultValue?: string }; export function ButtonGroup({ defaultValue, ...props }: ButtonGroupProps) { const [value, setValue] = React.useState(defaultValue); const { dir } = React.use(RovingFocusContext); return ( ); } type ButtonProps = Omit, 'value'> & { value?: string }; export function Button(props: ButtonProps) { const { value: contextValue, setValue } = React.use(ButtonGroupContext); const isSelected = contextValue !== undefined && props.value !== undefined && contextValue === props.value; return ( ); export const Chromatic = () => (

Some text with an inline accessible icon{' '}

); Chromatic.parameters = { chromatic: { disable: false } }; const CrossIcon = () => ( ); ================================================ FILE: apps/storybook/stories/accordion.stories.module.css ================================================ .root { font-family: sans-serif; &[data-orientation='horizontal'] { display: flex; max-width: 40em; height: 50vh; } &[data-orientation='vertical'] { max-width: 20em; } } .item { &[data-orientation='horizontal'] { display: flex; border-right: 1px solid var(--gray-1); } &[data-orientation='vertical'] { border-bottom: 1px solid var(--gray-1); } } .header { margin: 0; &[data-orientation='horizontal'] { height: 100%; } } .trigger { /* because it's a button, we want to stretch it */ &[data-orientation='horizontal'] { height: 100%; } &[data-orientation='vertical'] { width: 100%; } text-align: inherit; box-sizing: border-box; appearance: none; border: none; padding: 10px; background-color: black; color: var(--gray-1); font-family: inherit; font-size: 1.2em; &:focus { outline: 2px solid var(--red-8); color: var(--red-9); } &[data-disabled] { color: var(--gray-9); } &[data-state='open'] { background-color: var(--red-9); color: var(--gray-1); &:focus { color: var(--gray-12); } } } .content { padding: 10px; line-height: 1.5; } .animatedContent { overflow: hidden; &[data-state='open'] { animation: accordion-slideDown 300ms ease-out; } &[data-state='closed'] { animation: accordion-slideUp 300ms ease-out; } } .animated2DContent { overflow: hidden; &[data-state='open'] { animation: accordion-open2D 1000ms ease-out; } &[data-state='closed'] { animation: accordion-close2D 1000ms ease-out; } } .rootAttr, .itemAttr, .headerAttr, .triggerAttr, .contentAttr { background-color: var(--blue-a12); border: 2px solid var(--blue-9); padding: 10px; &[data-state='closed'] { border-color: var(--red-9); } &[data-state='open'] { border-color: var(--green-9); } &[data-disabled] { border-style: dashed; } &:disabled { opacity: 0.5; } } .contentAttr { /* ensure we can see the content (because it has `hidden` attribute) */ display: block; } @keyframes accordion-slideDown { from { height: 0; } to { height: var(--radix-accordion-content-height); } } @keyframes accordion-slideUp { from { height: var(--radix-accordion-content-height); } to { height: 0; } } @keyframes accordion-open2D { from { width: 0; height: 0; } to { width: var(--radix-accordion-content-width); height: var(--radix-accordion-content-height); } } @keyframes accordion-close2D { from { width: var(--radix-accordion-content-width); height: var(--radix-accordion-content-height); } to { width: 0; height: 0; } } ================================================ FILE: apps/storybook/stories/accordion.stories.tsx ================================================ /* eslint-disable jsx-a11y/anchor-is-valid */ import * as React from 'react'; import { Accordion } from 'radix-ui'; import styles from './accordion.stories.module.css'; export default { title: 'Components/Accordion' }; export const Single = () => { const [valueOne, setValueOne] = React.useState('one'); return ( <>

Uncontrolled

One Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. Two Cursus sed mattis commodo fermentum conubia ipsum pulvinar sagittis, diam eget bibendum porta nascetur ac dictum, leo tellus dis integer platea ultrices mi. Three (disabled) Sociis hac sapien turpis conubia sagittis justo dui, inceptos penatibus feugiat himenaeos euismod magna, nec tempor pulvinar eu etiam mattis. Four Odio placerat quisque sapien sagittis non sociis ligula penatibus dignissim vitae, enim vulputate nullam semper potenti etiam volutpat libero.

Controlled

One Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. Two Cursus sed mattis commodo fermentum conubia ipsum pulvinar sagittis, diam eget bibendum porta nascetur ac dictum, leo tellus dis integer platea ultrices mi. Three (disabled) Sociis hac sapien turpis conubia sagittis justo dui, inceptos penatibus feugiat himenaeos euismod magna, nec tempor pulvinar eu etiam mattis. Four Odio placerat quisque sapien sagittis non sociis ligula penatibus dignissim vitae, enim vulputate nullam semper potenti etiam volutpat libero.

Collapsible

One Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. Two Cursus sed mattis commodo fermentum conubia ipsum pulvinar sagittis, diam eget bibendum porta nascetur ac dictum, leo tellus dis integer platea ultrices mi. Three (disabled) Sociis hac sapien turpis conubia sagittis justo dui, inceptos penatibus feugiat himenaeos euismod magna, nec tempor pulvinar eu etiam mattis. Four Odio placerat quisque sapien sagittis non sociis ligula penatibus dignissim vitae, enim vulputate nullam semper potenti etiam volutpat libero. ); }; export const Multiple = () => { const [value, setValue] = React.useState(['one', 'two']); return ( <>

Uncontrolled

One Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. Two Cursus sed mattis commodo fermentum conubia ipsum pulvinar sagittis, diam eget bibendum porta nascetur ac dictum, leo tellus dis integer platea ultrices mi. Three (disabled) Sociis hac sapien turpis conubia sagittis justo dui, inceptos penatibus feugiat himenaeos euismod magna, nec tempor pulvinar eu etiam mattis. Four Odio placerat quisque sapien sagittis non sociis ligula penatibus dignissim vitae, enim vulputate nullam semper potenti etiam volutpat libero.

Controlled

One Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. Two Cursus sed mattis commodo fermentum conubia ipsum pulvinar sagittis, diam eget bibendum porta nascetur ac dictum, leo tellus dis integer platea ultrices mi. Three (disabled) Sociis hac sapien turpis conubia sagittis justo dui, inceptos penatibus feugiat himenaeos euismod magna, nec tempor pulvinar eu etiam mattis. Four Odio placerat quisque sapien sagittis non sociis ligula penatibus dignissim vitae, enim vulputate nullam semper potenti etiam volutpat libero. ); }; export const Animated = () => { const values = ['One', 'Two', 'Three', 'Four']; const [count, setCount] = React.useState(1); const [hasDynamicContent, setHasDynamicContent] = React.useState(false); const timerRef = React.useRef(0); React.useEffect(() => { if (hasDynamicContent) { timerRef.current = window.setTimeout(() => { setCount((prevCount) => { const nextCount = prevCount < 5 ? prevCount + 1 : prevCount; if (nextCount === 5) setHasDynamicContent(false); return nextCount; }); }, 3000); return () => { clearTimeout(timerRef.current); }; } }, [count, hasDynamicContent]); return ( <>

Closed by default

{values.map((value) => ( {value} {[...Array(count)].map((_, index) => (
Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed.
))}
))}

Open by default

{values.map((value) => ( {value} {[...Array(count)].map((_, index) => (
Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed.
))}
))}
); }; export const Animated2D = () => { const values = ['One', 'Two', 'Three', 'Four']; return ( <> {values.map((value) => ( {value}
Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed.
))}
); }; export const AnimatedControlled = () => { const [value, setValue] = React.useState(['one', 'two', 'three', 'four']); return ( One Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. Two Cursus sed mattis commodo fermentum conubia ipsum pulvinar sagittis, diam eget bibendum porta nascetur ac dictum, leo tellus dis integer platea ultrices mi. Three Sociis hac sapien turpis conubia sagittis justo dui, inceptos penatibus feugiat himenaeos euismod magna, nec tempor pulvinar eu etiam mattis. Four Odio placerat quisque sapien sagittis non sociis ligula penatibus dignissim vitae, enim vulputate nullam semper potenti etiam volutpat libero. ); }; export const OutsideViewport = () => ( <>

Scroll down to see tabs

When accordion buttons are focused and the user is navigating via keyboard, the page should not scroll unless the next tab is entering the viewport.

One Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. Two Cursus sed mattis commodo fermentum conubia ipsum pulvinar sagittis, diam eget bibendum porta nascetur ac dictum, leo tellus dis integer platea ultrices mi. Three (disabled) Sociis hac sapien turpis conubia sagittis justo dui, inceptos penatibus feugiat himenaeos euismod magna, nec tempor pulvinar eu etiam mattis. Four Odio placerat quisque sapien sagittis non sociis ligula penatibus dignissim vitae, enim vulputate nullam semper potenti etiam volutpat libero.
); export const Horizontal = () => ( <>

Horizontal Orientation

One Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. Two Cursus sed mattis commodo fermentum conubia ipsum pulvinar sagittis, diam eget bibendum porta nascetur ac dictum, leo tellus dis integer platea ultrices mi. Three (disabled) Sociis hac sapien turpis conubia sagittis justo dui, inceptos penatibus feugiat himenaeos euismod magna, nec tempor pulvinar eu etiam mattis. Four Odio placerat quisque sapien sagittis non sociis ligula penatibus dignissim vitae, enim vulputate nullam semper potenti etiam volutpat libero. ); export const Chromatic = () => { const items = ['One', 'Two', 'Three', 'Four']; return ( <>

Uncontrolled

Single closed

{items.map((item) => ( {item} {item}: Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. ))}

Single open

{items.map((item) => ( {item} {item}: Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. ))}

Multiple closed

{items.map((item) => ( {item} {item}: Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. ))}

Multiple open

{items.map((item) => ( {item} {item}: Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. ))}

Controlled

Single open

{items.map((item) => ( {item} {item}: Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. ))}

Multiple open

{items.map((item) => ( {item} {item}: Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. ))}

Disabled (whole)

{items.map((item) => ( {item} {item}: Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. ))}

Disabled (item)

Just item

{items.map((item) => ( {item} {item}: Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. ))}

with `disabled=false` on top-level

{items.map((item) => ( {item} {item}: Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. ))}

Force mounted contents

{items.map((item) => ( {item} {item}: Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. ))}

State attributes

Accordion disabled

{items.map((item) => ( {item} {item}: Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. ))}

Accordion enabled with item override

{items.map((item) => ( {item} {item}: Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. ))}

Accordion disabled with item override

{items.map((item) => ( {item} {item}: Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. ))} ); }; Chromatic.parameters = { chromatic: { disable: false } }; ================================================ FILE: apps/storybook/stories/alert-dialog.stories.module.css ================================================ .trigger { } .overlay, .overlayAttr { /* ensures overlay is positionned correctly */ position: fixed; inset: 0; /* --------- */ background-color: var(--gray-12); opacity: 0.2; } .content, .chromaticContent, .contentAttr { /* ensures good default position for content */ position: fixed; top: 0; left: 0; /* --------- */ top: 50%; left: 50%; transform: translate(-50%, -50%); background: var(--gray-1); min-width: 300px; min-height: 150px; padding: 50px; border-radius: 10px; background-color: var(--gray-1); box-shadow: 0 2px 10px var(--black-a6); } .cancel, .action { appearance: none; padding: 10px; border: none; } .cancel { background: var(--gray-3); color: var(--gray-12); } .action { background: var(--red-9); color: var(--gray-1); } .title { } .description { } .chromaticContent { padding: 10px; min-width: auto; min-height: auto; } .triggerAttr .overlayAttr, .contentAttr, .cancelAttr, .actionAttr, .titleAttr, .descriptionAttr { background-color: var(--blue-a12); border: 2px solid var(--blue-9); padding: 10px; &[data-state='closed'] { border-color: var(--red-9); } &[data-state='open'] { border-color: var(--green-9); } } ================================================ FILE: apps/storybook/stories/alert-dialog.stories.tsx ================================================ import * as React from 'react'; import { AlertDialog } from 'radix-ui'; import styles from './alert-dialog.stories.module.css'; export default { title: 'Components/AlertDialog' }; export const Styled = () => ( delete everything Are you sure? This will do a very dangerous thing. Thar be dragons! yolo, do it maybe not ); export const Controlled = () => { const [open, setOpen] = React.useState(false); const [housePurchased, setHousePurchased] = React.useState(false); return (
a large white house with a red roof
{ if (housePurchased) { e.preventDefault(); setHousePurchased(false); } }} > {housePurchased ? 'You bought the house! Sell it!' : 'Buy this house'} Are you sure? Houses are very expensive and it looks like you only have €20 in the bank. Maybe consult with a financial advisor? setHousePurchased(true)}> buy it anyway good point, I'll reconsider
); }; export const Chromatic = () => (

Uncontrolled

Closed

delete everything Title Description Confirm Cancel

Open

delete everything Title Description Confirm Cancel

Uncontrolled with reordered parts

Closed

Title Description Confirm Cancel delete everything

Open

Title Description Confirm Cancel delete everything

Controlled

Closed

delete everything Title Description Confirm Cancel

Open

delete everything Title Description Confirm Cancel

Controlled with reordered parts

Closed

Title Description Confirm Cancel delete everything

Open

Title Description Confirm Cancel delete everything

State attributes

Closed

delete everything Title Description Confirm Cancel

Open

delete everything Title Description Confirm Cancel
); Chromatic.parameters = { chromatic: { disable: false } }; ================================================ FILE: apps/storybook/stories/arrow.stories.tsx ================================================ import { Arrow } from 'radix-ui/internal'; export default { title: 'Utilities/Arrow' }; const RECOMMENDED_CSS__ARROW__ROOT = { // better default alignment verticalAlign: 'middle', }; export const Styled = () => ( ); export const CustomSizes = () => ( <> ); export const CustomArrow = () => (
); ================================================ FILE: apps/storybook/stories/aspect-ratio.stories.module.css ================================================ .root { display: flex; align-items: center; justify-content: center; background-color: var(--red-9); color: var(--gray-1); } ================================================ FILE: apps/storybook/stories/aspect-ratio.stories.tsx ================================================ import { AspectRatio } from 'radix-ui'; import styles from './aspect-ratio.stories.module.css'; export default { title: 'Components/AspectRatio' }; const image = ( A house in a forest ); export const Styled = () => (

Default ratio (1/1)

); export const CustomRatios = () => { return (
{image}
{image}
{image}
{image}
); }; export const Chromatic = () => ( <>

Default ratio

Default ratio (1/1)

Custom ratios

{image}
{image}
{image}
{image}
); Chromatic.parameters = { chromatic: { disable: false } }; ================================================ FILE: apps/storybook/stories/avatar.stories.module.css ================================================ .root { /* ensures image/fallback is centered */ display: inline-flex; align-items: center; justify-content: center; vertical-align: middle; /* ensures image doesn't bleed out */ overflow: hidden; /* ensures no selection is possible */ user-select: none; /* -------- */ border-radius: 9999px; width: 48px; height: 48px; } .image { /* ensures image is full size and not distorted */ width: 100%; height: 100%; object-fit: cover; } .fallback { /* ensures content inside the fallback is centered */ width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; /* -------- */ background-color: var(--gray-12); color: var(--gray-1); } ================================================ FILE: apps/storybook/stories/avatar.stories.tsx ================================================ import { Avatar } from 'radix-ui'; import styles from './avatar.stories.module.css'; import React from 'react'; export default { title: 'Components/Avatar' }; const src = 'https://picsum.photos/id/1005/400/400'; const srcAlternative = 'https://picsum.photos/id/1006/400/400'; const srcBroken = 'https://broken.link.com/broken-pic.jpg'; export const Styled = () => ( <>

Without image & with fallback

JS

With image & with fallback

JS

With image & with fallback (but broken src)

Changing image src

{(src) => ( JS )} ); export const Chromatic = () => ( <>

Without image & with fallback

JS

With image & with fallback

JS

With image & with fallback (but broken src)

Changing image src

{(src) => ( JS )} ); Chromatic.parameters = { chromatic: { disable: false, delay: 1000 } }; const AvatarIcon = () => ( ); function SourceChanger({ sources, children, }: { sources: [string, ...string[]]; children: (src: string) => React.ReactElement; }) { const [src, setSrc] = React.useState(sources[0]); React.useEffect(() => { const interval = setInterval(() => { const nextIndex = (sources.indexOf(src) + 1) % sources.length; setSrc(sources[nextIndex]!); }, 1000); return () => clearInterval(interval); }, [sources, src]); return children(src); } ================================================ FILE: apps/storybook/stories/checkbox.stories.module.css ================================================ .root { /* better default alignment */ vertical-align: middle; /* ------ */ border: 1px solid var(--gray-4); width: 30px; height: 30px; padding: 4px; &:focus { outline: none; border-color: var(--red-9); box-shadow: 0 0 0 1px var(--red-9); } &[data-disabled] { opacity: 0.3; } } .indicator { background-color: var(--red-9); display: block; width: 20px; height: 4px; &[data-state='checked'], &[data-state='unchecked'] { height: 20px; } } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } .animatedIndicator { transition: height 300ms; &[data-state='checked'] { animation: fadeIn 1000ms ease-out; } &[data-state='unchecked'] { animation: fadeOut 1000ms ease-in; } } .rootAttr, .indicatorAttr { background-color: var(--blue-a12); border: 2px solid var(--blue-9); padding: 10px; &[data-state='unchecked'] { border-color: var(--red-9); } &[data-state='checked'] { border-color: var(--green-9); } &[data-state='indeterminate'] { border-color: var(--purple-9); } &[data-disabled] { border-style: dashed; } &:disabled { opacity: 0.5; } } .label { /* ensures it can receive vertical margins */ display: inline-block; /* better default alignment */ vertical-align: middle; /* mimics default `label` tag (as we render a `span`) */ cursor: default; display: inline-block; } ================================================ FILE: apps/storybook/stories/checkbox.stories.tsx ================================================ /* eslint-disable react/jsx-pascal-case */ import * as React from 'react'; import { Checkbox, Label as LabelPrimitive } from 'radix-ui'; import styles from './checkbox.stories.module.css'; export default { title: 'Components/Checkbox' }; export const Styled = () => ( <>

This checkbox is nested inside a label. The state is uncontrolled.

Custom label



Native label

Native label + native checkbox

Custom label + htmlFor



Native label + htmlFor

Native label + native checkbox

); export const Controlled = () => { const [checked, setChecked] = React.useState(true); return ( <>

This checkbox is placed adjacent to its label. The state is controlled.

{' '} ); }; export const Indeterminate = () => { const [checked, setChecked] = React.useState('indeterminate'); return ( <>

); }; export const WithinForm = () => { const [data, setData] = React.useState({ optional: false, required: false, stopprop: false }); const [checked, setChecked] = React.useState('indeterminate'); return (
event.preventDefault()} onChange={(event) => { const input = event.target as HTMLInputElement; setData((prevData) => ({ ...prevData, [input.name]: input.checked })); }} >
optional checked: {String(data.optional)}



required checked: {String(data.required)}


stop propagation checked: {String(data.stopprop)} event.stopPropagation()} >


no bubble input checked: {String(data.stopprop)}


); }; export const LegacyStyled = () => ( <>

This checkbox is nested inside a label. The state is uncontrolled.

Custom label



Native label

Native label + native checkbox

Custom label + htmlFor



Native label + htmlFor

Native label + native checkbox

); export const LegacyControlled = () => { const [checked, setChecked] = React.useState(true); return ( <>

This checkbox is placed adjacent to its label. The state is controlled.

{' '} ); }; export const LegacyIndeterminate = () => { const [checked, setChecked] = React.useState('indeterminate'); return ( <>

); }; export const LegacyWithinForm = () => { const [data, setData] = React.useState({ optional: false, required: false, stopprop: false }); const [checked, setChecked] = React.useState('indeterminate'); return (
event.preventDefault()} onChange={(event) => { const input = event.target as HTMLInputElement; setData((prevData) => ({ ...prevData, [input.name]: input.checked })); }} >
optional checked: {String(data.optional)}



required checked: {String(data.required)}


stop propagation checked: {String(data.stopprop)} event.stopPropagation()} >


); }; export const LegacyAnimated = () => { const [checked, setChecked] = React.useState('indeterminate'); return ( <>

); }; export const LegacyChromatic = () => ( <>

Uncontrolled

Unchecked

Checked

Controlled

Unchecked

Checked

Indeterminate

Disabled

Force mounted indicator

State attributes

Unchecked

Checked

Indeterminate

Disabled

Force mounted indicator

); LegacyChromatic.parameters = { chromatic: { disable: false } }; const Label = (props: any) => ; ================================================ FILE: apps/storybook/stories/collapsible.stories.module.css ================================================ .root { max-width: 20em; font-family: sans-serif; } .trigger { /* because it's a button, we want to stretch it */ width: 100%; /* and remove center text alignment in favour of inheriting */ text-align: inherit; /* ---- */ appearance: none; border: none; padding: 10px; background-color: var(--color-black); color: white; font-family: inherit; font-size: 1.2em; --shadow-color: crimson; &:focus { outline: none; box-shadow: inset 0 -5px 0 0 var(--shadow-color); color: var(--color-red); } &[data-disabled] { color: var(--color-gray300); } &[data-state='open'] { background-color: var(--color-red); color: var(--color-white); &:focus { --shadow-color: #111; color: var(--color-black); } } } .content { padding: 10px; line-height: 1.5; } @keyframes collapsible-slideDown { from { height: 0; } to { height: var(--radix-collapsible-content-height); } } @keyframes collapsible-slideUp { from { height: var(--radix-collapsible-content-height); } to { height: 0; } } @keyframes collapsible-openRight { from { width: 0; } to { width: var(--radix-collapsible-content-width); } } @keyframes collapsible-closeRight { from { width: var(--radix-collapsible-content-width); } to { width: 0; } } .animatedContent { overflow: hidden; &[data-state='open'] { animation: collapsible-slideDown 300ms ease-out; } &[data-state='closed'] { animation: collapsible-slideUp 300ms ease-in; } } .animatedWidthContent { overflow: hidden; &[data-state='open'] { animation: collapsible-openRight 300ms ease-out; } &[data-state='closed'] { animation: collapsible-closeRight 300ms ease-in; } } .rootAttr, .triggerAttr, .contentAttr { /* ensure we can see the content (because it has `hidden` attribute) */ display: block; background-color: rgb(0 0 255 / 0.3); border: 2px solid blue; padding: 10px; &[data-state='closed'] { border-color: red; } &[data-state='open'] { border-color: green; } &[data-disabled] { border-style: dashed; } &:disabled { opacity: 0.5; } } ================================================ FILE: apps/storybook/stories/collapsible.stories.tsx ================================================ import * as React from 'react'; import { Collapsible } from 'radix-ui'; import styles from './collapsible.stories.module.css'; export default { title: 'Components/Collapsible' }; export const Styled = () => ( Trigger Content 1 ); export const Controlled = () => { const [open, setOpen] = React.useState(false); return ( {open ? 'close' : 'open'}
Content 1
); }; export const Animated = () => { return ( <>

Closed by default

Trigger
Content 1

Open by default

Trigger
Content 1
); }; export const AnimatedHorizontal = () => { return ( Trigger
Content
); }; export const Chromatic = () => ( <>

Uncontrolled

Closed

Trigger Content 1

Open

Trigger Content 1

Controlled

Closed

Trigger Content 1

Open

Trigger Content 1

Disabled

Trigger Content 1

State attributes

Closed

Trigger Content 1

Open

Trigger Content 1

Disabled

Trigger Content 1 ); Chromatic.parameters = { chromatic: { disable: false } }; ================================================ FILE: apps/storybook/stories/collection.stories.tsx ================================================ import * as React from 'react'; import { Collection as CollectionPrimitive } from 'radix-ui/internal'; const { createCollection } = CollectionPrimitive; export default { title: 'Utilities/Collection' }; export const Basic = () => ( Red Green Blue ); export const WithElementInBetween = () => (
Colors
Red Green Blue
Words
Hello World
); const Tomato = () => Tomato; export const WithWrappedItem = () => ( Red Green Blue ); export const WithFragment = () => { const countries = ( <> France UK Spain ); return ( {countries} ); }; export const DynamicInsertion = () => { const [hasTomato, setHasTomato] = React.useState(false); const [, forceUpdate] = React.useState(); return ( <> ); }; function WrappedItems({ hasTomato }: any) { return ( <> Red {hasTomato ? : null} Green Blue ); } export const WithChangingItem = () => { const [isDisabled, setIsDisabled] = React.useState(false); return ( <> Red Green Blue ); }; export const Nested = () => ( 1 2 2.1 2.2 2.3 3 ); /* ------------------------------------------------------------------------------------------------- * List implementation * -----------------------------------------------------------------------------------------------*/ type ItemData = { disabled: boolean }; const [Collection, useCollection] = createCollection, ItemData>( 'List', ); const List: React.FC<{ children: React.ReactNode }> = (props) => { return (
    ); }; type ItemProps = React.ComponentPropsWithRef<'li'> & { children: React.ReactNode; disabled?: boolean; }; function Item({ disabled = false, ...props }: ItemProps) { return (
  • ); } // Ensure that our implementation doesn't break if the item list/item is memoized const MemoItem = React.memo(Item); const MemoItems = React.memo(WrappedItems); function LogItems({ name = 'items' }: { name?: string }) { const getItems = useCollection(undefined); React.useEffect(() => console.log(name, getItems())); return null; } ================================================ FILE: apps/storybook/stories/context-menu.stories.module.css ================================================ .trigger { display: flex; align-items: center; justify-content: center; width: 200px; height: 100px; border: 2px dashed var(--color-black); border-radius: 6px; background-color: rgba(0, 0, 0, 0.1); &:focus { outline: none; box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.5); } &[data-state='open'] { background-color: lightblue; } } .content { display: inline-block; box-sizing: border-box; min-width: 130px; background-color: var(--color-white); border: 1px solid var(--color-gray100); border-radius: 6px; padding: 5px; box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.1); font-family: apple-system, BlinkMacSystemFont, helvetica, arial, sans-serif; font-size: 13px; &:focus-within { border-color: var(--color-black); } } .animatedContent { transform-origin: var(--radix-context-menu-content-transform-origin); &[data-state='open'] { animation: contextMenu-scaleIn 0.6s cubic-bezier(0.16, 1, 0.3, 1); } } .label, .item, .subTrigger { display: flex; align-items: center; justify-content: space-between; line-height: 1; cursor: default; user-select: none; white-space: nowrap; height: 25px; padding: 0 10px; color: var(--color-black); border-radius: 3px; } .label { color: var(--color-gray100); } .item, .subTrigger { outline: none; &[data-highlighted] { background-color: var(--color-black); color: var(--color-white); } &[data-disabled] { color: var(--color-gray100); } } .subTrigger { &:not([data-highlighted])[data-state='open'] { background-color: var(--color-gray100); color: var(--color-black); } } .separator { height: 1; margin: 5px 10px; background-color: var(--color-gray100); } @keyframes contextMenu-scaleIn { 0% { transform: scale(0) rotateZ(-10deg); } 20% { transform: scale(1.1); } 100% { transform: scale(1); } } ================================================ FILE: apps/storybook/stories/context-menu.stories.tsx ================================================ import * as React from 'react'; import { ContextMenu } from 'radix-ui'; import { foodGroups } from '@repo/test-data/foods'; import styles from './context-menu.stories.module.css'; export default { title: 'Components/ContextMenu' }; export const Styled = () => (
    Right click here console.log('undo')}> Undo console.log('redo')}> Redo console.log('cut')}> Cut console.log('copy')}> Copy console.log('paste')}> Paste
    ); export const Modality = () => (

    Modal (default)

    console.log('undo')}> Undo console.log('redo')}> Redo Submenu → console.log('one')}> One console.log('two')}> Two Submenu → console.log('one')} > One console.log('two')} > Two console.log('three')} > Three console.log('three')}> Three console.log('cut')} > Cut console.log('copy')}> Copy console.log('paste')}> Paste