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
================================================
[](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
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
================================================
/// SSR / RSC testing
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.
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.
Lacinia hendrerit auctor nam quisque augue suscipit feugiat, sit at imperdiet vitae lacus. Dolor sit dui posuere faucibus non pharetra laoreet conubia, augue rhoncus cras nisl sodales proin hac ipsum, per hendrerit sed volutpat natoque curae consectetur. Curae blandit neque vehicula vel mauris vulputate per felis sociosqu, sodales integer sollicitudin id litora accumsan viverra pulvinar, mus non adipiscing dolor facilisis habitasse mi leo. Litora faucibus eu pulvinar tempus gravida iaculis consectetur risus euismod fringilla, dui posuere viverra sapien tortor mattis et dolor tempor sem conubia, taciti sociis mus rhoncus cubilia praesent dapibus aliquet quis. Diam hendrerit aliquam metus dolor fusce lorem, non gravida arcu primis posuere ipsum adipiscing, mus sollicitudin eros lacinia mollis.
Habitant fames mi massa mollis fusce congue nascetur magna bibendum inceptos accumsan, potenti ipsum ac sollicitudin taciti dis rhoncus lacinia fermentum placerat. Himenaeos taciti egestas lacinia maecenas ornare ultricies, auctor vitae nulla mi posuere leo mollis, eleifend lacus rutrum ante curabitur. Nullam mi quisque nulla enim pretium facilisi interdum morbi, himenaeos velit fames pellentesque eget nascetur laoreet vel rutrum, malesuada risus ad netus dolor et scelerisque.
Habitasse tristique hac ligula in metus blandit lobortis leo nullam litora, tempus fusce tincidunt phasellus urna est rhoncus pretium etiam eu, fames neque faucibus sociis primis felis dui vitae odio. Egestas purus morbi pulvinar luctus adipiscing rutrum ultrices hac, vehicula odio ridiculus cubilia vivamus blandit faucibus, dapibus velit sociis metus ultricies amet scelerisque.
Scelerisque commodo nam cras litora lacinia primis fames morbi natoque, quisque ante duis phasellus pharetra convallis montes felis. Consectetur leo suspendisse fringilla elementum maecenas massa urna malesuada auctor senectus, pretium turpis nisi orci ipsum vulputate cubilia sociis adipiscing. Vulputate ridiculus amet dis accumsan non ultrices fames mattis hendrerit, ornare elementum sociosqu eget consectetur duis viverra vivamus tincidunt, blandit nulla porta semper dolor pharetra nisi scelerisque. Consequat conubia porta cras et ac auctor pellentesque luctus morbi potenti, viverra varius commodo venenatis vestibulum erat sagittis laoreet.
Some text with an inline accessible icon{' '}
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.
Default ratio (1/1)
This checkbox is nested inside a label. The state is uncontrolled.
This checkbox is placed adjacent to its label. The state is controlled.
{' '}
This checkbox is nested inside a label. The state is uncontrolled.
This checkbox is placed adjacent to its label. The state is controlled.
{' '}
Selected file: {file}
These elements can't be focused when the dialog is opened.
> ); export const CustomFocus = () => { const firstNameRef = React.useRefThe search input will receive the focus after closing the dialog.
Data: {JSON.stringify(data, null, 2)}
>
);
};
async function wait(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
================================================
FILE: apps/storybook/stories/hover-card.stories.module.css
================================================
.content {
transform-origin: var(--radix-hover-card-content-transform-origin);
/* ----- */
background-color: var(--color-gray300);
padding: 20px;
border-radius: 5px;
}
.arrow {
fill: var(--color-gray300);
}
@keyframes hoverCard-fadeIn {
from {
transform: scale(0.9);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
@keyframes hoverCard-fadeOut {
from {
transform: scale(1);
opacity: 1;
}
to {
transform: scale(0.9);
opacity: 0;
}
}
.animatedContent {
&[data-state='open'] {
animation: hoverCard-fadeIn 250ms ease;
}
&[data-state='closed'] {
animation: hoverCard-fadeOut 250ms ease;
}
}
.grid {
display: inline-grid;
grid-template-columns: repeat(3, 50px);
column-gap: 150px;
row-gap: 100px;
padding: 100px;
border: 1px solid black;
}
.contentAttr,
.arrowAttr,
.triggerAttr {
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;
}
}
.chromaticArrow {
fill: black;
}
.chromaticContent,
.contentAttr {
display: grid;
place-content: center;
width: 60px;
height: 60px;
background-color: royalblue;
color: white;
font-size: 10px;
border: 1px solid rgb(0 0 0 / 0.3);
}
.chromaticTrigger {
width: 30px;
height: 30px;
background-color: tomato;
border: 1px solid rgb(0 0 0 / 0.3);
}
================================================
FILE: apps/storybook/stories/hover-card.stories.tsx
================================================
import * as React from 'react';
import { Dialog, HoverCard } from 'radix-ui';
import { Popper } from 'radix-ui/internal';
import styles from './hover-card.stories.module.css';
const { SIDE_OPTIONS, ALIGN_OPTIONS } = Popper;
export default { title: 'Components/HoverCard' };
const contentClass = ({ animated }: { animated?: boolean }) =>
[
styles.content, ///
animated && styles.animatedContent,
]
.filter(Boolean)
.join(' ');
export const Basic = () => {
return (
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer feugiat mattis malesuada. Fusce elementum vulputate aliquet. Integer fringilla porta eros. Ut ultricies mattis nisi. Sed et tempor massa. Sed non arcu ut velit scelerisque bibendum tempor sed mi. In non consequat sapien. Donec sollicitudin eget tellus ut venenatis. Donec posuere sem ante, nec iaculis arcu varius sit amet. Praesent non tortor quam. Curabitur dapibus justo a commodo ornare.
Suspendisse eleifend consequat iaculis. Nunc bibendum velit felis, nec vulputate purus egestas quis. Integer mauris dui, pulvinar non metus id, tristique dignissim elit. Vivamus massa tellus, porttitor id lorem non, molestie aliquam dolor. Pellentesque erat quam, pellentesque non metus id, tempus sagittis massa.
Sed at elementum sem, non venenatis leo. Ut vulputate consectetur finibus. Sed nunc lectus, accumsan in nisl et, vehicula pretium nisi. Vivamus vestibulum ante quis urna consequat, ultrices condimentum sem commodo. Pellentesque eget orci laoreet, feugiat purus sed, maximus nisi. Suspendisse commodo venenatis facilisis.
{side}
{align}
{side}
{align}
{side}
{align}
{side}
{align}
{side}
{align}
See instances on the periphery of the page.
{SIDES.map((side) => ALIGN_OPTIONS.map((align) => (
{side}
{align}
For comparison
try the closed select below
(relying on `.textContent` — default)
(with explicit `textValue` prop)
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam purus odio, vestibulum in dictum et, sagittis vel nibh. Fusce placerat arcu lorem, a scelerisque odio fringilla sit amet. Suspendisse volutpat sed diam ut cursus. Nulla facilisi. Ut at volutpat nibh. Nullam justo mi, elementum vitae ex eu,{' '} gravida dictum metus. Morbi vulputate consectetur cursus. Fusce vitae nisi nunc. Suspendisse pellentesque aliquet tincidunt. Aenean molestie pulvinar ipsum.
{side}
{align}
{side}
{align}
{side}
{align}
{side}
{align}
{side}
{align}
See instances on the periphery of the page.
{SIDES.map((side) => ALIGN_OPTIONS.map((align) => (
{side}
{align}
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.
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.
This content is rendered in a portal inside Container A but appears inside Container B because we have used Container B as a container element for the Portal.
Container A
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.
Container B
This content is rendered in a portal inside Container B but appears inside Container C because we have used Container C as a container element for the Portal.
Container C
See squares in the top-left
Deferred animation should unmount correctly when toggled. Content will flash briefly while we wait for animation to be applied.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sit amet eros iaculis, bibendum tellus ac, lobortis odio. Aliquam bibendum elit est, in iaculis est commodo id. Donec pulvinar est libero. Proin consectetur pellentesque molestie. Fusce mi ante, ullamcorper eu ante finibus, finibus pellentesque turpis. Mauris convallis, leo in vulputate varius, sapien lectus suscipit eros, ac semper odio sapien sit amet magna. Sed mattis turpis et lacinia ultrices. Nulla a commodo mauris. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque id tempor metus. Pellentesque faucibus tortor non nisi maximus dignissim. Etiam leo nisi, molestie a porttitor at, euismod a libero. Nullam placerat tristique enim nec pulvinar. Sed eleifend dictum nulla a aliquam. Sed tempus ipsum eget urna posuere aliquam. Nulla maximus tortor dui, sed laoreet odio aliquet ac. Vestibulum dolor orci, lacinia finibus vehicula eget, posuere ac lectus. Quisque non felis at ipsum scelerisque condimentum. In pharetra semper arcu, ut hendrerit sem auctor vel. Aliquam non lacinia elit, a facilisis ante. Praesent eget eros augue. Praesent nunc orci, ullamcorper non pulvinar eu, elementum id nibh. Nam id lorem euismod, sodales augue quis, porttitor magna. Vivamus ut nisl velit. Nam ultrices maximus felis, quis ullamcorper quam luctus et.
); ================================================ FILE: apps/storybook/stories/select.stories.module.css ================================================ .trigger { display: flex; align-items: center; gap: 5px; border: 1px solid var(--color-black); border-radius: 6px; background-color: transparent; height: 50px; padding: 5px 15px; font-family: -apple-system, BlinkMacSystemFont, helvetica, arial, sans-serif; font-size: 13px; line-height: 1; overflow: hidden; &:focus { outline: none; box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.5); } } .content { background-color: var(--color-white); border: 1px solid var(--color-gray100); border-radius: 6px; box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.1); position: relative; &:focus-within { border-color: var(--color-black); } min-width: var(--radix-select-trigger-width); max-height: var(--radix-select-content-available-height); } .contentWithPadding { padding: 5px; } .viewport { padding: 5px; } .group { } .label, .item { display: flex; align-items: center; line-height: 1; cursor: default; user-select: none; white-space: nowrap; height: 25px; padding: 0 25px; font-family: -apple-system, BlinkMacSystemFont, helvetica, arial, sans-serif; font-size: 13px; color: var(--color-black); border-radius: 3px; } .label { color: var(--color-gray300); font-weight: 500; } .item { position: relative; outline: none; &:active { background-color: var(--color-gray100); } &[data-highlighted] { background-color: var(--color-black); color: white; } &[data-disabled] { color: var(--color-gray100); } [dir='rtl'] & { font-size: 16px; font-weight: bold; } } .itemInGroup { padding-left: 35px; } .indicator { position: absolute; left: 6px; top: 6px; & :global(svg) { display: block; } [dir='rtl'] & { left: auto; right: 6px; } } .scrollButton { display: flex; align-items: center; justify-content: center; box-sizing: border-box; height: 25px; background-color: var(--color-white); color: var(--color-black); cursor: default; user-select: none; } .scrollUpButton { border-bottom: 1px solid rgba(0, 0, 0, 0.2); border-top-left-radius: 6px; border-top-right-radius: 6px; } .scrollDownButton { border-top: 1px solid rgba(0, 0, 0, 0.2); border-bottom-left-radius: 6px; border-bottom-right-radius: 6px; } .separator { height: 1px; margin: 5px -5px; background-color: var(--color-gray100); } ================================================ FILE: apps/storybook/stories/select.stories.tsx ================================================ import * as React from 'react'; import { Dialog, Select, Label as LabelPrimitive } from 'radix-ui'; import { foodGroups } from '@repo/test-data/foods'; import styles from './select.stories.module.css'; const Label = LabelPrimitive.Root; export default { title: 'Components/Select' }; const scrollUpButtonClass = [styles.scrollUpButton, styles.scrollButton].join(' '); const scrollDownButtonClass = [styles.scrollDownButton, styles.scrollButton].join(' '); const POSITIONS = ['item-aligned', 'popper'] as const; export const Styled = () => (The following separator is horizontal and has semantic meaning.
The following separator is horizontal and is purely decorative. Assistive technology will ignore this element.
The following separator is vertical and has semantic meaning.
The following separator is vertical and is purely decorative. Assistive technology will ignore this element.
Check the console for the `onValueCommit` log
> ); export const RightToLeft = () => (Should NOT have delete button next to component
Should NOT have delete button next to component
Should have delete button next to component
This switch is nested inside a label. The state is uncontrolled.
This switch is placed adjacent to its label. The state is controlled.
Should not animate on initial mount
Some paragraph
Start video call press{' '} c
The first button will display AND enunciate the label.
The second button will display the label, but enunciate the aria label.
Hello this is a test with{' '}
Content remains open while moving pointer to it
Tooltip closes when pointer leaves the trigger
{side}
{align}
{side}
{align}
{side}
{align}
{side}
{align}
{side}
{align}
See instances on the periphery of the page.
{SIDES.map((side) => ALIGN_OPTIONS.map((align) => (
{side}
{align}
= P & { __scopeAccordion?: Scope };
const [createAccordionContext, createAccordionScope] = createContextScope(ACCORDION_NAME, [
createCollectionScope,
createCollapsibleScope,
]);
const useCollapsibleScope = createCollapsibleScope();
type AccordionElement = AccordionImplMultipleElement | AccordionImplSingleElement;
interface AccordionSingleProps extends AccordionImplSingleProps {
type: 'single';
}
interface AccordionMultipleProps extends AccordionImplMultipleProps {
type: 'multiple';
}
const Accordion = React.forwardRef = P & { __scopeAlertDialog?: Scope };
const [createAlertDialogContext, createAlertDialogScope] = createContextScope(ROOT_NAME, [
createDialogScope,
]);
const useDialogScope = createDialogScope();
type DialogProps = React.ComponentPropsWithoutRef = P & { __scopeAvatar?: Scope };
const [createAvatarContext, createAvatarScope] = createContextScope(AVATAR_NAME);
type ImageLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error';
type AvatarContextValue = {
imageLoadingStatus: ImageLoadingStatus;
onImageLoadingStatusChange(status: ImageLoadingStatus): void;
};
const [AvatarProvider, useAvatarContext] = createAvatarContext = P & { __scopeCheckbox?: Scope };
const [createCheckboxContext, createCheckboxScope] = createContextScope(CHECKBOX_NAME);
type CheckedState = boolean | 'indeterminate';
type CheckboxContextValue = P & { __scopeCollapsible?: Scope };
const [createCollapsibleContext, createCollapsibleScope] = createContextScope(COLLAPSIBLE_NAME);
type CollapsibleContextValue = {
contentId: string;
disabled?: boolean;
open: boolean;
onOpenToggle(): void;
};
const [CollapsibleProvider, useCollapsibleContext] =
createCollapsibleContext