diff --git a/.circleci/config.yml b/.circleci/config.yml
index 98e3d657ae..f96ae44433 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -43,7 +43,7 @@ default-job: &default-job
REACT_VERSION: << parameters.react-version >>
TEST_GATE: << parameters.test-gate >>
AWS_REGION_ARTIFACTS: eu-central-1
- working_directory: /tmp/material-ui
+ working_directory: /tmp/base-ui
docker:
- image: cimg/node:18.20
@@ -89,7 +89,7 @@ commands:
corepack enable
- run:
name: Prepare playwright hash
- command: pnpm list --json --filter playwright > /tmp/playwright_info.json
+ command: pnpm list --recursive --json --filter @mui-internal/tests playwright > /tmp/playwright_info.json
- store_artifacts:
name: Debug playwright hash
path: /tmp/playwright_info.json
@@ -156,7 +156,7 @@ jobs:
name: Should not have any git not staged
command: git add -A && git diff --exit-code --staged
- run:
- name: Check for duplicated packages
+ name: '`pnpm dedupe` was run?'
command: |
# #default-branch-switch
if [[ $(git diff --name-status master | grep -E 'pnpm-workspace\.yaml|pnpm-lock.yaml|package\.json') == "" ]];
@@ -319,7 +319,7 @@ jobs:
<<: *default-job
resource_class: 'medium+'
docker:
- - image: mcr.microsoft.com/playwright:v1.43.1-focal
+ - image: mcr.microsoft.com/playwright:v1.46.1-focal
environment:
NODE_ENV: development # Needed if playwright is in `devDependencies`
steps:
@@ -350,7 +350,7 @@ jobs:
test_profile:
<<: *default-job
docker:
- - image: mcr.microsoft.com/playwright:v1.43.1-focal
+ - image: mcr.microsoft.com/playwright:v1.46.1-focal
environment:
NODE_ENV: development # Needed if playwright is in `devDependencies`
steps:
@@ -378,7 +378,7 @@ jobs:
test_regressions:
<<: *default-job
docker:
- - image: mcr.microsoft.com/playwright:v1.43.1-focal
+ - image: mcr.microsoft.com/playwright:v1.46.1-focal
environment:
NODE_ENV: development # Needed if playwright is in `devDependencies`
steps:
@@ -395,13 +395,16 @@ jobs:
test_e2e:
<<: *default-job
docker:
- - image: mcr.microsoft.com/playwright:v1.43.1-focal
+ - image: mcr.microsoft.com/playwright:v1.46.1-focal
environment:
NODE_ENV: development # Needed if playwright is in `devDependencies`
steps:
- checkout
- install_js:
browsers: true
+ - run:
+ name: install Playwright browsers
+ command: pnpm exec playwright install
- run:
name: pnpm test:e2e
command: pnpm test:e2e
diff --git a/.eslintignore b/.eslintignore
index f7de1dac5e..a32c2b2373 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -14,6 +14,7 @@
/tmp
.next
build
+build-tests
node_modules
.nyc_output
pnpm-lock.yaml
diff --git a/.eslintrc.js b/.eslintrc.js
index 2244d79083..6885f32f2c 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,6 +1,11 @@
const baseline = require('@mui/monorepo/.eslintrc');
const path = require('path');
+const OneLevelImportMessage = [
+ 'Prefer one level nested imports to avoid bundling everything in dev mode or breaking CJS/ESM split.',
+ 'See https://github.com/mui/material-ui/pull/24147 for the kind of win it can unlock.',
+].join('\n');
+
module.exports = {
...baseline,
settings: {
@@ -16,8 +21,30 @@ module.exports = {
*/
rules: {
...baseline.rules,
- // TODO move to @mui/monorepo, codebase is moving away from default exports
+ // TODO move to @mui/monorepo, codebase is moving away from default exports https://github.com/mui/material-ui/issues/21862
'import/prefer-default-export': 'off',
+ 'import/export': 'off', // Mostly handled by Typescript itself. ESLint produces false positives with declaration merging.
+ 'no-restricted-imports': [
+ 'error',
+ {
+ patterns: [
+ {
+ group: [
+ '@mui/*/*/*',
+ '@pigment-css/*/*/*',
+ '@base_ui/react/*/*',
+ '!@base_ui/react/legacy/*',
+ // Allow any import depth with any internal packages
+ '!@mui/internal-*/**',
+ // TODO delete, @mui/docs should be @mui/internal-docs
+ '!@mui/docs/**',
+ ],
+ message: OneLevelImportMessage,
+ },
+ ],
+ },
+ ],
+ '@typescript-eslint/no-redeclare': 'off',
},
overrides: [
...baseline.overrides,
@@ -30,5 +57,15 @@ module.exports = {
'no-console': 'off',
},
},
+ {
+ files: ['packages/**/*.test{.tsx,.js}'],
+ excludedFiles: 'packages/mui-base/src/legacy/**/*.*',
+ extends: ['plugin:testing-library/react'],
+ rules: {
+ 'testing-library/prefer-screen-queries': 'off', // TODO: enable and fix
+ 'testing-library/no-container': 'off', // TODO: enable and fix
+ 'testing-library/render-result-naming-convention': 'off', // False positives
+ },
+ },
],
};
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000000..2a367f8645
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,5 @@
+* @atomiks @michaldudak
+/docs/data/ @atomiks @colmtuite @michaldudak
+/examples/ @atomiks @colmtuite @michaldudak
+/packages/mui-base/ @atomiks @colmtuite @michaldudak
+/scripts/ @michaldudak
diff --git a/.github/ISSUE_TEMPLATE/1.bug.yml b/.github/ISSUE_TEMPLATE/1.bug.yml
index cc3c360e71..d7e80d17b0 100644
--- a/.github/ISSUE_TEMPLATE/1.bug.yml
+++ b/.github/ISSUE_TEMPLATE/1.bug.yml
@@ -59,3 +59,9 @@ body:
Output from `npx @mui/envinfo` goes here.
```
+ - type: markdown
+ attributes:
+ value: |
+ ## :heart: Love Base UI?
+
+ Consider donating $10 to sustain our open-source work: [https://opencollective.com/mui-org](https://opencollective.com/mui-org).
diff --git a/.github/ISSUE_TEMPLATE/2.feature.yml b/.github/ISSUE_TEMPLATE/2.feature.yml
index 64065b37c8..d215d51558 100644
--- a/.github/ISSUE_TEMPLATE/2.feature.yml
+++ b/.github/ISSUE_TEMPLATE/2.feature.yml
@@ -32,3 +32,9 @@ body:
attributes:
label: Motivation
description: What are you trying to accomplish? Providing context helps us come up with a solution that is more useful in the real world.
+ - type: markdown
+ attributes:
+ value: |
+ ## :heart: Love Base UI?
+
+ Consider donating $10 to sustain our open-source work: [https://opencollective.com/mui-org](https://opencollective.com/mui-org).
diff --git a/.github/ISSUE_TEMPLATE/4.docs-feedback.yml b/.github/ISSUE_TEMPLATE/4.docs-feedback.yml
index 0d6b1fb2c3..5c88a7b20e 100644
--- a/.github/ISSUE_TEMPLATE/4.docs-feedback.yml
+++ b/.github/ISSUE_TEMPLATE/4.docs-feedback.yml
@@ -42,3 +42,9 @@ body:
attributes:
label: Context
description: What are you trying to accomplish? Providing context helps us come up with a solution that is more useful in the real world.
+ - type: markdown
+ attributes:
+ value: |
+ ## :heart: Love Base UI?
+
+ Consider donating $10 to sustain our open-source work: [https://opencollective.com/mui-org](https://opencollective.com/mui-org).
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index f901ad8cb8..9186efbffe 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,4 +1,4 @@
contact_links:
- name: Support ❔
- url: https://mui.com/getting-started/support/
+ url: https://mui.com/base-ui/getting-started/support/
about: I need support with Base UI.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index abf1ff9ef5..a0a67c01b8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,10 +7,6 @@ on:
# We don't need to run CI twice (push+pull_request)
- 'renovate/**'
pull_request:
- paths-ignore:
- # should sync with ci-check.yml as a workaround to bypass github checks
- - 'docs/**'
- - 'examples/**'
permissions: {}
@@ -25,14 +21,14 @@ jobs:
os: [macos-latest, windows-latest, ubuntu-latest]
steps:
- run: echo "${{ github.actor }}"
- - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
# fetch all tags which are required for `pnpm release:changelog`
fetch-depth: 0
- name: Set up pnpm
uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d # v3.0.0
- name: Use Node.js 18.x
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 18
cache: 'pnpm' # https://github.com/actions/setup-node/blob/main/docs/advanced-usage.md#caching-packages-dependencies
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 440aea6468..71c1bc1f9d 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -16,10 +16,10 @@ jobs:
security-events: write
steps:
- name: Checkout repository
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3
+ uses: github/codeql-action/init@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
with:
languages: typescript
config-file: ./.github/codeql/codeql-config.yml
@@ -30,4 +30,4 @@ jobs:
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3
+ uses: github/codeql-action/analyze@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml
index 487e4cb502..cfc9752720 100644
--- a/.github/workflows/maintenance.yml
+++ b/.github/workflows/maintenance.yml
@@ -29,7 +29,7 @@ jobs:
steps:
- run: echo "${{ github.actor }}"
- name: check if prs are dirty
- uses: eps1lon/actions-label-merge-conflict@e62d7a53ff8be8b97684bffb6cfbbf3fc1115e2e # v3.0.0
+ uses: eps1lon/actions-label-merge-conflict@1b1b1fcde06a9b3d089f3464c96417961dde1168 # v3.0.2
with:
dirtyLabel: 'PR: out-of-date'
removeOnDirtyLabel: 'PR: ready to ship'
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index 69f8a46048..d3252e8d2c 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -22,12 +22,12 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
persist-credentials: false
- name: Run analysis
- uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
+ uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
with:
results_file: results.sarif
results_format: sarif
@@ -43,6 +43,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: Upload to code-scanning
- uses: github/codeql-action/upload-sarif@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3
+ uses: github/codeql-action/upload-sarif@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
with:
sarif_file: results.sarif
diff --git a/.github/workflows/support-stackoverflow.yml b/.github/workflows/support-stackoverflow.yml
index c9d8fa6bb0..381370f9ed 100644
--- a/.github/workflows/support-stackoverflow.yml
+++ b/.github/workflows/support-stackoverflow.yml
@@ -22,14 +22,14 @@ jobs:
# Comment to post on issues marked as support requests. Add a link
# to a support page, or set to `false` to disable
issue-comment: |
- 👋 Thanks for using MUI Core!
+ 👋 Thanks for using this project!
We use GitHub issues exclusively as a bug and feature requests tracker, however,
this issue appears to be a support request.
- For support, please check out https://mui.com/getting-started/support/. Thanks!
+ For support with Base UI please check out https://mui.com/base-ui/getting-started/support/. Thanks!
- If you have a question on Stack Overflow, you are welcome to link to it here, it might help others.
+ If you have a question on Stack Overflow, you are welcome to link to it here, it might help others.
If your issue is subsequently confirmed as a bug, and the report follows the issue template, it can be reopened.
close-issue: true
lock-issue: false
diff --git a/.github/workflows/vale-action.yml b/.github/workflows/vale-action.yml
index 3fc7d562e1..7a76e0d2c4 100644
--- a/.github/workflows/vale-action.yml
+++ b/.github/workflows/vale-action.yml
@@ -12,7 +12,7 @@ jobs:
contents: read
pull-requests: write
steps:
- - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: errata-ai/vale-action@38bf078c328061f59879b347ca344a718a736018 # v2.1.0
continue-on-error: true
with:
diff --git a/.gitignore b/.gitignore
index 8dd0f7c555..a48dc934e1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,7 @@
# created by netlify dev (to perform local debug)
.netlify
build
+build-tests
node_modules
package-lock.json
size-snapshot.json
diff --git a/.mocharc.js b/.mocharc.js
index 9425179503..6fd107f5c0 100644
--- a/.mocharc.js
+++ b/.mocharc.js
@@ -10,7 +10,11 @@ module.exports = {
recursive: true,
timeout: (process.env.CIRCLECI === 'true' ? 5 : 2) * 1000, // Circle CI has low-performance CPUs.
reporter: 'dot',
- require: ['@mui/internal-test-utils/setupBabel', '@mui/internal-test-utils/setupJSDOM'],
+ require: [
+ '@mui/internal-test-utils/setupBabel',
+ '@mui/internal-test-utils/setupJSDOM',
+ './packages/mui-base/test/setup.ts',
+ ],
'watch-ignore': [
// default
'.git',
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a79661989f..bb7912268f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,85 @@
# Versions
+## v1.0.0-alpha.2
+
+_Aug 19, 2024_
+
+A big thanks to the 10 contributors who made this release possible. Here are some highlights ✨:
+
+⭐ We added many new components: AlertDialog, Dialog, Field, Menu, Popover, PreviewCard, Progress, Slider, and Tooltip.
+
+### `@base_ui/react@1.0.0-alpha.2`
+
+- [Checkbox] Fix checked change when clicking button with wrapping label (#467) @atomiks
+- [Dialog] Create new component and hook (#372) @michaldudak
+- [Field] Create new Field components (#477) @atomiks
+- [Menu] Overhaul the component API (#468) @michaldudak
+- [NumberField] Fix tests on non-English locale machines (#524) @michaldudak
+- [NumberField] Rename `onChange` prop to `onValueChange` (#464) @atomiks
+- [Popover] Component and Hook (#381) @atomiks
+- [Popover] Fix `keepMounted` focus management (#489) @atomiks
+- [Popover] Wait for focus to settle in tests (#491) @michaldudak
+- [PreviewCard] Create new component (#469) @atomiks
+- [PreviewCard] Fix Firefox browser hang (#490) @atomiks
+- [Progress] New `Progress` components (#470) @mj12albert
+- [Slider] improve `disabled` prop description (#527) @sai6855
+- [Slider] New Slider components and hook (#373) @mj12albert
+- [Switch/Checkbox] Rename `onChange` prop to `onCheckedChange` (#465) @atomiks
+- [Tabs] Fix indicator tests (#379) @michaldudak
+- [Tooltip] Component and Hook (#264) @atomiks
+- [Tooltip] Fix animations (#426) @atomiks
+- [useCompoundParent] Display `displayName` only in dev (#525) @sai6855
+
+### Docs
+
+- [docs] Add badges like in Material UI @oliviertassinari
+- [docs] Add the logo to the README (#448) @danilo-leal
+- [docs] Convert alpha component docs to new docs template (#392) @colmtuite
+- [docs] Correct Bundlephobia links (#419) @michaldudak
+- [docs] Fix page description line break @oliviertassinari
+- [docs] Fix the X link (#450) @michaldudak
+- [docs] Fix Vale errors (#492) @oliviertassinari
+- [docs] Prepare security table for once it has its first release (#536) @oliviertassinari
+- [docs] Update twitter.com to x.com @oliviertassinari
+- [docs][Tooltip] Use the correct version of ComponentLinkHeader (#425) @michaldudak
+
+### Core
+
+- [code-infra] Fix pnpm version in package.json engines (#409) @Janpot
+- [code-infra] Propagate API docs builder package interface changes (#478) @LukasTy
+- [code-infra] Remove raw-loader (#404) @michaldudak
+- [code-infra] Use shared .stylelintrc.js config (#415) @oliviertassinari
+- [core] Add `trackAnchor` prop for anchor positioning (#519) @atomiks
+- [core] Add `useAnchorPositioning` Hook (#461) @atomiks
+- [core] Add `useTransitionStatus` and `useExecuteIfNotAnimated` Hooks (#396) @atomiks
+- [core] Add codeowners file (#447) @michaldudak
+- [core] Allow Renovate to update pnpm (#446) @michaldudak
+- [core] Encapsulate the common rendering logic in `useComponentRenderer` (#408) @michaldudak
+- [core] Fix event naming convention @oliviertassinari
+- [core] Improve performance of `mergeReactProps` (#456) @marcpachecog
+- [core] Improve Tooltip and Popover consistency (#463) @atomiks
+- [core] Link GH issue for import/prefer-default-export @oliviertassinari
+- [core] Make pnpm version permissive (#529) @atomiks
+- [core] Move hooks under component directories (#405) @michaldudak
+- [core] Move legacy components to a subdirectory (#410) @michaldudak
+- [core] Normalize rest / other to match the most common used @oliviertassinari
+- [core] Refactor animation hooks (#417) @atomiks
+- [core] Remove sources of old components we don't intend to support (#474) @michaldudak
+- [core] Simpler pnpm dedupe error message to act on @oliviertassinari
+- [core] Upgrade to core-js v3 (#418) @atomiks
+- [core] Verify types in test code (#457) @michaldudak
+- [dependencies] Do not try to update eslint (#515) @michaldudak
+- [docs-infra] Fix a stylelint issue (#421) @oliviertassinari
+- [docs-infra] Integrate the latest @mui/docs (#378) @michaldudak
+- [test] Clean up and unify test code (#532) @michaldudak
+- [test] Update test-utils and remove enzyme (#473) @michaldudak
+- [test] Use internal-test-utils from npm (#424) @michaldudak
+- [typescript] Add `type` to export statements (#544) @michaldudak
+- [website] Fix /base-ui/ code duplication (#416) @oliviertassinari- [infra] Add support donation button @oliviertassinari
+- [website] Redirect to an existing page on dev (#445) @michaldudak
+
+All contributors of this release in alphabetical order: @atomiks, @colmtuite, @danilo-leal, @Janpot, @LukasTy, @marcpachecog, @michaldudak, @mj12albert, @oliviertassinari, @sai6855
+
## v1.0.0-alpha.1
diff --git a/CHANGELOG.old.md b/CHANGELOG.old.md
index 62dd089893..141a6d803a 100644
--- a/CHANGELOG.old.md
+++ b/CHANGELOG.old.md
@@ -1186,7 +1186,7 @@ _Nov 16, 2021_
- [core] Rename mui/core to mui/base (#29585) @michaldudak
- Based on the results of the [poll](https://twitter.com/michaldudak/status/1452630484706635779) and our internal discussions, we decided to rename the `@mui/core` package to `@mui/base`. The main rationale for this is the fact that we use the term "Core" to refer to the core components product family, the one that includes Material Design components, unstyled components, System utilities, etc. Therefore, @mui/core was effectively a subset of MUI Core. This was confusing.
+ Based on the results of the [poll](https://x.com/michaldudak/status/1452630484706635779) and our internal discussions, we decided to rename the `@mui/core` package to `@mui/base`. The main rationale for this is the fact that we use the term "Core" to refer to the core components product family, the one that includes Material Design components, unstyled components, System utilities, etc. Therefore, @mui/core was effectively a subset of MUI Core. This was confusing.
The new name better reflects the purpose of the package: it contains unstyled components, hooks, and utilities that serve as a **base** to build on.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9cbba88951..5686e6d3b1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,6 +1,6 @@
# Contributing to Base UI
-## Base UI vs. Material UI
+## Base UI vs. MUI organization
Base UI is an open-source project of the MUI organization. The repositories are part of the same codebase.
`mui/base-ui` imports the code infrastructure from [`mui/material-ui`](https://github.com/mui/material-ui).
diff --git a/README.md b/README.md
index ec3c87be03..717b477d51 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,26 @@
+
+
+
+
+
+
+
+
+
-Base UI
-
-[Base UI](https://mui.com/base-ui/) is a library of unstyled React UI components and hooks. With Base UI, you gain complete control over your app's CSS and accessibility features.
+
+Base UI is an unstyled UI component library for building accessible user interfaces while maintaining complete control over styling.
+
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/mui/base-ui/blob/HEAD/LICENSE)
[![npm latest package](https://img.shields.io/npm/v/@base_ui/react/latest.svg)](https://www.npmjs.com/package/@base_ui/react)
[![npm downloads](https://img.shields.io/npm/dm/@base_ui/react.svg)](https://www.npmjs.com/package/@base_ui/react)
-[![CircleCI](https://circleci.com/gh/mui/base-ui/tree/master.svg?style=shield)](https://app.circleci.com/pipelines/github/mui/base-ui?branch=master)
-[![Coverage status](https://img.shields.io/codecov/c/github/mui/base-ui/master.svg)](https://codecov.io/gh/mui/base-ui/branch/master)
-[![Follow on X](https://img.shields.io/twitter/follow/MUI_hq.svg?label=follow+MUI)](https://twitter.com/MUI_hq)
+[![GitHub branch status](https://img.shields.io/github/checks-status/mui/base-ui/HEAD)](https://github.com/mui/base-ui/commits/HEAD/)
+[![Coverage status](https://img.shields.io/codecov/c/github/mui/base-ui.svg)](https://app.codecov.io/gh/mui/base-ui/)
+[![Follow on X](https://img.shields.io/twitter/follow/Base_UI.svg?label=follow+Base+UI)](https://x.com/Base_UI)
[![Renovate status](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://github.com/mui/base-ui/issues/2)
[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/mui/base-ui.svg)](https://isitmaintained.com/project/mui/base-ui 'Average time to resolve an issue')
[![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/mui-org)](https://opencollective.com/mui-org)
@@ -19,12 +28,14 @@
+---
+
## Documentation
-Visit [https://mui.com/base-ui/](https://mui.com/base-ui/) to view the full documentation.
+Visit [mui.com/base-ui/getting-started](https://mui.com/base-ui/getting-started/) to view the full documentation.
-**Note**: Base UI's API is currently being revised; there will be no new features or components added to the current implementation.
-Learn more about plans for Base UI in [this blog post](https://mui.com/blog/base-ui-2024-plans/).
+> **Note**: Base UI's API is currently being revised; there will be no new features or components added to the current implementation.
+> Learn more about the roadmap in [this blog post](https://mui.com/blog/base-ui-2024-plans/).
## Sponsors
@@ -40,8 +51,6 @@ Diamond sponsors are those who have pledged \$1,500/month or more to MUI.
### Gold 🏆
-via [Open Collective](https://opencollective.com/mui-org) or via [Patreon](https://www.patreon.com/oliviertassinari)
-
@@ -54,6 +63,7 @@ via [Open Collective](https://opencollective.com/mui-org) or via [Patreon](http
Gold sponsors are those who have pledged \$500/month or more to MUI.
+Via [Open Collective](https://opencollective.com/mui-org) or via [Patreon](https://www.patreon.com/oliviertassinari).
### More backers
diff --git a/SECURITY.md b/SECURITY.md
index 5f24d987cf..b347ad65c0 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -1,5 +1,13 @@
# Security policy
+## Supported versions
+
+The versions of the project that are currently supported with security updates.
+
+| Base UI version | Release | Supported |
+| --------------: | :------ | :----------------- |
+| <=1.0.0 | TBD | :white_check_mark: |
+
## Reporting a vulnerability
You can report a vulnerability by contacting us via email at [security@mui.com](mailto:security@mui.com).
diff --git a/docs/data/base/components/accordion/accordion.md b/docs/data/base/components/accordion/accordion.md
deleted file mode 100644
index a8b814bb19..0000000000
--- a/docs/data/base/components/accordion/accordion.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-productId: base-ui
-title: React Accordion component
-githubLabel: 'component: accordion'
-waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/accordion/
----
-
-# Accordion 🚧
-
-Accordions let users show and hide sections of related content on a page.
-
-:::warning
-The Base UI Accordion component isn't available yet, but you can upvote [this GitHub issue](https://github.com/mui/base-ui/issues/25) to see it arrive sooner.
-:::
diff --git a/docs/data/base/components/alert-dialog/AlertDialogIntroduction/css/index.js b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/css/index.js
new file mode 100644
index 0000000000..6c34a8e923
--- /dev/null
+++ b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/css/index.js
@@ -0,0 +1,117 @@
+import * as React from 'react';
+import * as AlertDialog from '@base_ui/react/AlertDialog';
+import { useTheme } from '@mui/system';
+
+export default function AlertDialogIntroduction() {
+ return (
+
+
+ Subscribe
+
+
+ Subscribe
+
+ Are you sure you want to subscribe?
+
+
+
+
+
+
+ );
+}
+
+function useIsDarkMode() {
+ const theme = useTheme();
+ return theme.palette.mode === 'dark';
+}
+
+const grey = {
+ 900: '#0f172a',
+ 800: '#1e293b',
+ 700: '#334155',
+ 500: '#64748b',
+ 300: '#cbd5e1',
+ 200: '#e2e8f0',
+ 100: '#f1f5f9',
+ 50: '#f8fafc',
+};
+
+function Styles() {
+ // Replace this with your app logic for determining dark mode
+ const isDarkMode = useIsDarkMode();
+
+ return (
+
+ );
+}
diff --git a/docs/data/base/components/alert-dialog/AlertDialogIntroduction/css/index.tsx b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/css/index.tsx
new file mode 100644
index 0000000000..6c34a8e923
--- /dev/null
+++ b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/css/index.tsx
@@ -0,0 +1,117 @@
+import * as React from 'react';
+import * as AlertDialog from '@base_ui/react/AlertDialog';
+import { useTheme } from '@mui/system';
+
+export default function AlertDialogIntroduction() {
+ return (
+
+
+ Subscribe
+
+
+ Subscribe
+
+ Are you sure you want to subscribe?
+
+
+
+
+
+
+ );
+}
+
+function useIsDarkMode() {
+ const theme = useTheme();
+ return theme.palette.mode === 'dark';
+}
+
+const grey = {
+ 900: '#0f172a',
+ 800: '#1e293b',
+ 700: '#334155',
+ 500: '#64748b',
+ 300: '#cbd5e1',
+ 200: '#e2e8f0',
+ 100: '#f1f5f9',
+ 50: '#f8fafc',
+};
+
+function Styles() {
+ // Replace this with your app logic for determining dark mode
+ const isDarkMode = useIsDarkMode();
+
+ return (
+
+ );
+}
diff --git a/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.js b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.js
new file mode 100644
index 0000000000..6f2f0907cd
--- /dev/null
+++ b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.js
@@ -0,0 +1,104 @@
+import * as React from 'react';
+import * as AlertDialog from '@base_ui/react/AlertDialog';
+import { styled } from '@mui/system';
+
+export default function AlertDialogIntroduction() {
+ return (
+
+ Subscribe
+
+
+ Subscribe
+ Are you sure you want to subscribe?
+
+ Yes
+ No
+
+
+
+ );
+}
+
+const grey = {
+ 900: '#0f172a',
+ 800: '#1e293b',
+ 700: '#334155',
+ 500: '#64748b',
+ 300: '#cbd5e1',
+ 200: '#e2e8f0',
+ 100: '#f1f5f9',
+ 50: '#f8fafc',
+};
+
+const TriggerButton = styled(AlertDialog.Trigger)(
+ ({ theme }) => `
+ background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ border: none;
+ font-family: "IBM Plex Sans", sans-serif;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
+ }
+`,
+);
+
+const Popup = styled(AlertDialog.Popup)(
+ ({ theme }) => `
+ background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
+ min-width: 400px;
+ border-radius: 4px;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ font-family: "IBM Plex Sans", sans-serif;
+ transform: translate(-50%, -50%);
+ padding: 16px;
+ z-index: 2100;
+`,
+);
+
+const Controls = styled('div')(
+ ({ theme }) => `
+ display: flex;
+ flex-direction: row-reverse;
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ gap: 8px;
+ padding: 16px;
+ margin: 32px -16px -16px;
+`,
+);
+
+const CloseButton = styled(AlertDialog.Close)(
+ ({ theme }) => `
+ background-color: transparent;
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
+ color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ font-family: "IBM Plex Sans", sans-serif;
+ min-width: 80px;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ }
+`,
+);
+
+const Title = styled(AlertDialog.Title)`
+ font-size: 1.25rem;
+`;
+
+const Description = styled(AlertDialog.Description)``;
+
+const Backdrop = styled(AlertDialog.Backdrop)`
+ background: rgb(0 0 0 / 0.35);
+ position: fixed;
+ inset: 0;
+ backdrop-filter: blur(4px);
+ z-index: 2000;
+`;
diff --git a/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.tsx b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.tsx
new file mode 100644
index 0000000000..6f2f0907cd
--- /dev/null
+++ b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.tsx
@@ -0,0 +1,104 @@
+import * as React from 'react';
+import * as AlertDialog from '@base_ui/react/AlertDialog';
+import { styled } from '@mui/system';
+
+export default function AlertDialogIntroduction() {
+ return (
+
+ Subscribe
+
+
+ Subscribe
+ Are you sure you want to subscribe?
+
+ Yes
+ No
+
+
+
+ );
+}
+
+const grey = {
+ 900: '#0f172a',
+ 800: '#1e293b',
+ 700: '#334155',
+ 500: '#64748b',
+ 300: '#cbd5e1',
+ 200: '#e2e8f0',
+ 100: '#f1f5f9',
+ 50: '#f8fafc',
+};
+
+const TriggerButton = styled(AlertDialog.Trigger)(
+ ({ theme }) => `
+ background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ border: none;
+ font-family: "IBM Plex Sans", sans-serif;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
+ }
+`,
+);
+
+const Popup = styled(AlertDialog.Popup)(
+ ({ theme }) => `
+ background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
+ min-width: 400px;
+ border-radius: 4px;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ font-family: "IBM Plex Sans", sans-serif;
+ transform: translate(-50%, -50%);
+ padding: 16px;
+ z-index: 2100;
+`,
+);
+
+const Controls = styled('div')(
+ ({ theme }) => `
+ display: flex;
+ flex-direction: row-reverse;
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ gap: 8px;
+ padding: 16px;
+ margin: 32px -16px -16px;
+`,
+);
+
+const CloseButton = styled(AlertDialog.Close)(
+ ({ theme }) => `
+ background-color: transparent;
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
+ color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ font-family: "IBM Plex Sans", sans-serif;
+ min-width: 80px;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ }
+`,
+);
+
+const Title = styled(AlertDialog.Title)`
+ font-size: 1.25rem;
+`;
+
+const Description = styled(AlertDialog.Description)``;
+
+const Backdrop = styled(AlertDialog.Backdrop)`
+ background: rgb(0 0 0 / 0.35);
+ position: fixed;
+ inset: 0;
+ backdrop-filter: blur(4px);
+ z-index: 2000;
+`;
diff --git a/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.tsx.preview b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.tsx.preview
new file mode 100644
index 0000000000..b074c5cbb8
--- /dev/null
+++ b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.tsx.preview
@@ -0,0 +1,12 @@
+
+ Subscribe
+
+
+ Subscribe
+ Are you sure you want to subscribe?
+
+ Yes
+ No
+
+
+
\ No newline at end of file
diff --git a/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.js b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.js
new file mode 100644
index 0000000000..c051ff4a3d
--- /dev/null
+++ b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.js
@@ -0,0 +1,72 @@
+import * as React from 'react';
+import * as AlertDialog from '@base_ui/react/AlertDialog';
+
+export default function UnstyledDialogIntroduction() {
+ return (
+
+ Subscribe
+
+
+ Subscribe
+ Are you sure you want to subscribe?
+
+ Yes
+ No
+
+
+
+ );
+}
+
+function TriggerButton(props) {
+ const className = `
+ bg-slate-900 dark:bg-slate-50 text-slate-50 dark:text-slate-900
+ py-2 px-4 rounded min-w-[80px] border-none font-sans
+ hover:bg-slate-700 dark:hover:bg-slate-200`;
+
+ return ;
+}
+
+function Popup(props) {
+ const className = `
+ bg-slate-50 dark:bg-slate-900 border-[1px] border-solid border-slate-100 dark:border-slate-700
+ min-w-[400px] rounded shadow-xl fixed top-2/4 left-2/4 z-[2100]
+ -translate-x-2/4 -translate-y-2/4 p-4`;
+
+ return ;
+}
+
+function Controls(props) {
+ return (
+
+ );
+}
+
+function CloseButton(props) {
+ const className = `
+ bg-transparent border-[1px] border-solid border-slate-500 dark:border-slate-300
+ text-slate-900 dark:text-slate-50 py-2 px-4 rounded font-sans min-w-[80px]
+ hover:bg-slate-200 dark:hover:bg-slate-700`;
+
+ return ;
+}
+
+function Title(props) {
+ return ;
+}
+
+function Description(props) {
+ return ;
+}
+
+function Backdrop(props) {
+ return (
+
+ );
+}
diff --git a/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx
new file mode 100644
index 0000000000..07d9cddfbf
--- /dev/null
+++ b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx
@@ -0,0 +1,72 @@
+import * as React from 'react';
+import * as AlertDialog from '@base_ui/react/AlertDialog';
+
+export default function UnstyledDialogIntroduction() {
+ return (
+
+ Subscribe
+
+
+ Subscribe
+ Are you sure you want to subscribe?
+
+ Yes
+ No
+
+
+
+ );
+}
+
+function TriggerButton(props: AlertDialog.TriggerProps) {
+ const className = `
+ bg-slate-900 dark:bg-slate-50 text-slate-50 dark:text-slate-900
+ py-2 px-4 rounded min-w-[80px] border-none font-sans
+ hover:bg-slate-700 dark:hover:bg-slate-200`;
+
+ return ;
+}
+
+function Popup(props: AlertDialog.PopupProps) {
+ const className = `
+ bg-slate-50 dark:bg-slate-900 border-[1px] border-solid border-slate-100 dark:border-slate-700
+ min-w-[400px] rounded shadow-xl fixed top-2/4 left-2/4 z-[2100]
+ -translate-x-2/4 -translate-y-2/4 p-4`;
+
+ return ;
+}
+
+function Controls(props: React.ComponentPropsWithoutRef<'div'>) {
+ return (
+
+ );
+}
+
+function CloseButton(props: AlertDialog.CloseProps) {
+ const className = `
+ bg-transparent border-[1px] border-solid border-slate-500 dark:border-slate-300
+ text-slate-900 dark:text-slate-50 py-2 px-4 rounded font-sans min-w-[80px]
+ hover:bg-slate-200 dark:hover:bg-slate-700`;
+
+ return ;
+}
+
+function Title(props: AlertDialog.TitleProps) {
+ return ;
+}
+
+function Description(props: AlertDialog.DescriptionProps) {
+ return ;
+}
+
+function Backdrop(props: AlertDialog.BackdropProps) {
+ return (
+
+ );
+}
diff --git a/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx.preview b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx.preview
new file mode 100644
index 0000000000..b074c5cbb8
--- /dev/null
+++ b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx.preview
@@ -0,0 +1,12 @@
+
+ Subscribe
+
+
+ Subscribe
+ Are you sure you want to subscribe?
+
+ Yes
+ No
+
+
+
\ No newline at end of file
diff --git a/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.js b/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.js
new file mode 100644
index 0000000000..7e8e7fd592
--- /dev/null
+++ b/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.js
@@ -0,0 +1,135 @@
+import * as React from 'react';
+import * as BaseAlertDialog from '@base_ui/react/AlertDialog';
+import { styled } from '@mui/system';
+
+export default function AlertDialogWithTransitions() {
+ return (
+
+ Open
+
+ Animated alert dialog
+
+ This alert dialog uses CSS transitions on entry and exit.
+
+
+ Close
+
+
+
+
+ );
+}
+
+const grey = {
+ 900: '#0f172a',
+ 800: '#1e293b',
+ 700: '#334155',
+ 500: '#64748b',
+ 300: '#cbd5e1',
+ 200: '#e2e8f0',
+ 100: '#f1f5f9',
+ 50: '#f8fafc',
+};
+
+const Popup = styled(BaseAlertDialog.Popup)(
+ ({ theme }) => `
+ background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
+ min-width: 400px;
+ border-radius: 4px;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ font-family: IBM Plex Sans;
+ padding: 16px;
+ z-index: 2100;
+ transition-property: opacity, transform;
+ transition-duration: 150ms;
+ transition-timing-function: ease-in;
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+
+ &[data-state='open'] {
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(1);
+ transition-timing-function: ease-out;
+ }
+
+ &[data-entering] {
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+ }
+`,
+);
+
+const Backdrop = styled(BaseAlertDialog.Backdrop)`
+ background-color: rgb(0 0 0 / 0.2);
+ position: fixed;
+ inset: 0;
+ z-index: 2000;
+ backdrop-filter: blur(0);
+ opacity: 0;
+ transition-property: opacity, backdrop-filter;
+ transition-duration: 250ms;
+ transition-timing-function: ease-in;
+
+ &[data-state='open'] {
+ backdrop-filter: blur(6px);
+ opacity: 1;
+ transition-timing-function: ease-out;
+ }
+
+ &[data-entering] {
+ backdrop-filter: blur(0);
+ opacity: 0;
+ }
+`;
+
+const Title = styled(BaseAlertDialog.Title)`
+ font-size: 1.25rem;
+`;
+
+const Trigger = styled(BaseAlertDialog.Trigger)(
+ ({ theme }) => `
+ background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ border: none;
+ font-family:
+ "IBM Plex Sans",
+ sans-serif;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
+ }
+`,
+);
+
+const Close = styled(BaseAlertDialog.Close)(
+ ({ theme }) => `
+ background-color: transparent;
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
+ color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ font-family: IBM Plex Sans, sans-serif;
+ min-width: 80px;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ }
+`,
+);
+
+const Controls = styled('div')(
+ ({ theme }) => `
+ display: flex;
+ flex-direction: row-reverse;
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ gap: 8px;
+ padding: 16px;
+ margin: 32px -16px -16px;
+`,
+);
diff --git a/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.tsx b/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.tsx
new file mode 100644
index 0000000000..7e8e7fd592
--- /dev/null
+++ b/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.tsx
@@ -0,0 +1,135 @@
+import * as React from 'react';
+import * as BaseAlertDialog from '@base_ui/react/AlertDialog';
+import { styled } from '@mui/system';
+
+export default function AlertDialogWithTransitions() {
+ return (
+
+ Open
+
+ Animated alert dialog
+
+ This alert dialog uses CSS transitions on entry and exit.
+
+
+ Close
+
+
+
+
+ );
+}
+
+const grey = {
+ 900: '#0f172a',
+ 800: '#1e293b',
+ 700: '#334155',
+ 500: '#64748b',
+ 300: '#cbd5e1',
+ 200: '#e2e8f0',
+ 100: '#f1f5f9',
+ 50: '#f8fafc',
+};
+
+const Popup = styled(BaseAlertDialog.Popup)(
+ ({ theme }) => `
+ background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
+ min-width: 400px;
+ border-radius: 4px;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ font-family: IBM Plex Sans;
+ padding: 16px;
+ z-index: 2100;
+ transition-property: opacity, transform;
+ transition-duration: 150ms;
+ transition-timing-function: ease-in;
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+
+ &[data-state='open'] {
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(1);
+ transition-timing-function: ease-out;
+ }
+
+ &[data-entering] {
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+ }
+`,
+);
+
+const Backdrop = styled(BaseAlertDialog.Backdrop)`
+ background-color: rgb(0 0 0 / 0.2);
+ position: fixed;
+ inset: 0;
+ z-index: 2000;
+ backdrop-filter: blur(0);
+ opacity: 0;
+ transition-property: opacity, backdrop-filter;
+ transition-duration: 250ms;
+ transition-timing-function: ease-in;
+
+ &[data-state='open'] {
+ backdrop-filter: blur(6px);
+ opacity: 1;
+ transition-timing-function: ease-out;
+ }
+
+ &[data-entering] {
+ backdrop-filter: blur(0);
+ opacity: 0;
+ }
+`;
+
+const Title = styled(BaseAlertDialog.Title)`
+ font-size: 1.25rem;
+`;
+
+const Trigger = styled(BaseAlertDialog.Trigger)(
+ ({ theme }) => `
+ background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ border: none;
+ font-family:
+ "IBM Plex Sans",
+ sans-serif;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
+ }
+`,
+);
+
+const Close = styled(BaseAlertDialog.Close)(
+ ({ theme }) => `
+ background-color: transparent;
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
+ color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ font-family: IBM Plex Sans, sans-serif;
+ min-width: 80px;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ }
+`,
+);
+
+const Controls = styled('div')(
+ ({ theme }) => `
+ display: flex;
+ flex-direction: row-reverse;
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ gap: 8px;
+ padding: 16px;
+ margin: 32px -16px -16px;
+`,
+);
diff --git a/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.tsx.preview b/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.tsx.preview
new file mode 100644
index 0000000000..75f8ff39c9
--- /dev/null
+++ b/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.tsx.preview
@@ -0,0 +1,13 @@
+
+ Open
+
+ Animated alert dialog
+
+ This alert dialog uses CSS transitions on entry and exit.
+
+
+ Close
+
+
+
+
\ No newline at end of file
diff --git a/docs/data/base/components/alert-dialog/NestedAlertDialogs.js b/docs/data/base/components/alert-dialog/NestedAlertDialogs.js
new file mode 100644
index 0000000000..5c1385314c
--- /dev/null
+++ b/docs/data/base/components/alert-dialog/NestedAlertDialogs.js
@@ -0,0 +1,163 @@
+import * as React from 'react';
+import * as BaseAlertDialog from '@base_ui/react/AlertDialog';
+import { styled } from '@mui/system';
+
+export default function NestedAlertDialogs() {
+ return (
+
+ Open
+
+
+ Alert Dialog 1
+
+
+ Open Nested
+
+
+ Alert Dialog 2
+
+
+ Open Nested
+
+
+ Alert Dialog 3
+
+ Close
+
+
+
+ Close
+
+
+
+ Close
+
+
+
+ );
+}
+
+const grey = {
+ 900: '#0f172a',
+ 800: '#1e293b',
+ 700: '#334155',
+ 500: '#64748b',
+ 300: '#cbd5e1',
+ 200: '#e2e8f0',
+ 100: '#f1f5f9',
+ 50: '#f8fafc',
+};
+
+const Popup = styled(BaseAlertDialog.Popup)(
+ ({ theme }) => `
+ --transition-duration: 150ms;
+ background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
+ min-width: 400px;
+ border-radius: 4px;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ font-family: IBM Plex Sans;
+ padding: 16px;
+ z-index: 2100;
+ transform: translate(-50%, -35%) scale(0.8, calc(pow(0.95, var(--nested-dialogs))))
+ translateY(calc(-30px * var(--nested-dialogs)));
+ visibility: hidden;
+ opacity: 0.5;
+ transition:
+ transform var(--transition-duration) ease-in,
+ opacity var(--transition-duration) ease-in,
+ visibility var(--transition-duration) step-end;
+
+ &[data-state='open'] {
+ @starting-style {
+ & {
+ transform: translate(-50%, -35%) scale(0.8) translateY(0);
+ opacity: 0.5;
+ }
+ }
+
+ visibility: visible;
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(calc(pow(0.95, var(--nested-dialogs))))
+ translateY(calc(-30px * var(--nested-dialogs)));
+ transition:
+ transform var(--transition-duration) ease-out,
+ opacity var(--transition-duration) ease-out,
+ visibility var(--transition-duration) step-start;
+ }
+`,
+);
+
+const Title = styled(BaseAlertDialog.Title)`
+ font-size: 1.25rem;
+`;
+
+const Trigger = styled(BaseAlertDialog.Trigger)(
+ ({ theme }) => `
+ background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ border: none;
+ font-family:
+ "IBM Plex Sans",
+ sans-serif;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
+ }
+`,
+);
+
+const Backdrop = styled(BaseAlertDialog.Backdrop)`
+ background-color: rgb(0 0 0 / 0.2);
+ position: fixed;
+ inset: 0;
+ z-index: 2000;
+ backdrop-filter: blur(0);
+ opacity: 0;
+ transition-property: opacity, backdrop-filter;
+ transition-duration: 250ms;
+ transition-timing-function: ease-in;
+
+ &[data-state='open'] {
+ backdrop-filter: blur(6px);
+ opacity: 1;
+ transition-timing-function: ease-out;
+ }
+
+ &[data-entering] {
+ backdrop-filter: blur(0);
+ opacity: 0;
+ }
+`;
+
+const Close = styled(BaseAlertDialog.Close)(
+ ({ theme }) => `
+ background-color: transparent;
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
+ color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ font-family: IBM Plex Sans, sans-serif;
+ min-width: 80px;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ }
+`,
+);
+
+const Controls = styled('div')(
+ ({ theme }) => `
+ display: flex;
+ flex-direction: row-reverse;
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ gap: 8px;
+ padding: 16px;
+ margin: 32px -16px -16px;
+`,
+);
diff --git a/docs/data/base/components/alert-dialog/NestedAlertDialogs.tsx b/docs/data/base/components/alert-dialog/NestedAlertDialogs.tsx
new file mode 100644
index 0000000000..5c1385314c
--- /dev/null
+++ b/docs/data/base/components/alert-dialog/NestedAlertDialogs.tsx
@@ -0,0 +1,163 @@
+import * as React from 'react';
+import * as BaseAlertDialog from '@base_ui/react/AlertDialog';
+import { styled } from '@mui/system';
+
+export default function NestedAlertDialogs() {
+ return (
+
+ Open
+
+
+ Alert Dialog 1
+
+
+ Open Nested
+
+
+ Alert Dialog 2
+
+
+ Open Nested
+
+
+ Alert Dialog 3
+
+ Close
+
+
+
+ Close
+
+
+
+ Close
+
+
+
+ );
+}
+
+const grey = {
+ 900: '#0f172a',
+ 800: '#1e293b',
+ 700: '#334155',
+ 500: '#64748b',
+ 300: '#cbd5e1',
+ 200: '#e2e8f0',
+ 100: '#f1f5f9',
+ 50: '#f8fafc',
+};
+
+const Popup = styled(BaseAlertDialog.Popup)(
+ ({ theme }) => `
+ --transition-duration: 150ms;
+ background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
+ min-width: 400px;
+ border-radius: 4px;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ font-family: IBM Plex Sans;
+ padding: 16px;
+ z-index: 2100;
+ transform: translate(-50%, -35%) scale(0.8, calc(pow(0.95, var(--nested-dialogs))))
+ translateY(calc(-30px * var(--nested-dialogs)));
+ visibility: hidden;
+ opacity: 0.5;
+ transition:
+ transform var(--transition-duration) ease-in,
+ opacity var(--transition-duration) ease-in,
+ visibility var(--transition-duration) step-end;
+
+ &[data-state='open'] {
+ @starting-style {
+ & {
+ transform: translate(-50%, -35%) scale(0.8) translateY(0);
+ opacity: 0.5;
+ }
+ }
+
+ visibility: visible;
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(calc(pow(0.95, var(--nested-dialogs))))
+ translateY(calc(-30px * var(--nested-dialogs)));
+ transition:
+ transform var(--transition-duration) ease-out,
+ opacity var(--transition-duration) ease-out,
+ visibility var(--transition-duration) step-start;
+ }
+`,
+);
+
+const Title = styled(BaseAlertDialog.Title)`
+ font-size: 1.25rem;
+`;
+
+const Trigger = styled(BaseAlertDialog.Trigger)(
+ ({ theme }) => `
+ background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ border: none;
+ font-family:
+ "IBM Plex Sans",
+ sans-serif;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
+ }
+`,
+);
+
+const Backdrop = styled(BaseAlertDialog.Backdrop)`
+ background-color: rgb(0 0 0 / 0.2);
+ position: fixed;
+ inset: 0;
+ z-index: 2000;
+ backdrop-filter: blur(0);
+ opacity: 0;
+ transition-property: opacity, backdrop-filter;
+ transition-duration: 250ms;
+ transition-timing-function: ease-in;
+
+ &[data-state='open'] {
+ backdrop-filter: blur(6px);
+ opacity: 1;
+ transition-timing-function: ease-out;
+ }
+
+ &[data-entering] {
+ backdrop-filter: blur(0);
+ opacity: 0;
+ }
+`;
+
+const Close = styled(BaseAlertDialog.Close)(
+ ({ theme }) => `
+ background-color: transparent;
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
+ color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ font-family: IBM Plex Sans, sans-serif;
+ min-width: 80px;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ }
+`,
+);
+
+const Controls = styled('div')(
+ ({ theme }) => `
+ display: flex;
+ flex-direction: row-reverse;
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ gap: 8px;
+ padding: 16px;
+ margin: 32px -16px -16px;
+`,
+);
diff --git a/docs/data/base/components/alert-dialog/alert-dialog.md b/docs/data/base/components/alert-dialog/alert-dialog.md
new file mode 100644
index 0000000000..0c7225a2e1
--- /dev/null
+++ b/docs/data/base/components/alert-dialog/alert-dialog.md
@@ -0,0 +1,298 @@
+---
+productId: base-ui
+title: React Alert Dialog component
+components: AlertDialogBackdrop, AlertDialogClose, AlertDialogDescription, AlertDialogPopup, AlertDialogRoot, AlertDialogTitle, AlertDialogTrigger
+githubLabel: 'component: alert-dialog'
+waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/
+---
+
+# Alert Dialog
+
+Dialogs inform users about a task and can contain critical information, require decisions, or involve multiple tasks.
+
+{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}
+
+{{"component": "modules/components/ComponentPageTabs.js"}}
+
+{{"demo": "AlertDialogIntroduction", "defaultCodeOpen": false, "bg": "gradient"}}
+
+## Installation
+
+Base UI components are all available as a single package.
+
+
+
+```bash npm
+npm install @base_ui/react
+```
+
+```bash yarn
+yarn add @base_ui/react
+```
+
+```bash pnpm
+pnpm add @base_ui/react
+```
+
+
+
+Once you have the package installed, import the component.
+
+```ts
+import * as AlertDialog from '@base_ui/react/AlertDialog';
+```
+
+## Anatomy
+
+Alert Dialogs are implemented using a collection of related components:
+
+- ` ` is a top-level component that facilitates communication between other components. It does not render to the DOM.
+- ` ` is the alert dialog panel itself.
+- ` ` is the background element appearing when a popup is visible. Use it to indicate that the page is inert. The Backdrop must be a sibling of the Popup component.
+- ` ` is the component (a button by default) that, when clicked, shows the popup. When it's not provided, the visibility of the Alert Dialog can be controlled with its `open` prop (see [Controlled vs. uncontrolled behavior](#controlled-vs-uncontrolled-behavior)).
+- ` ` renders a button that closes the popup. You can attach your own click handlers to it to perform additional actions.
+- ` ` is an header element displaying the title of the alert dialog. It is referenced in the Dialog's ARIA attributes to properly announce it.
+- ` ` is an element describing of the dialog. It is referenced in the Dialog's ARIA attributes to properly announce it.
+
+```tsx
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Alert dialogs vs. dialogs
+
+The Alert Dialog is in many ways similar to the [Dialog](/base-ui/react-dialog/) component.
+Alert dialogs should be used in cases where the normal user's workflow needs to be interrupted to get a response.
+Therefore alert dialogs are always modal and cannot be dismissed any other way than by pressing a button inside them.
+
+## Controlled vs. uncontrolled behavior
+
+The simplest way to control the visibility of the alert dialog is to use the `AlertDialog.Trigger` and `AlertDialog.Close` components.
+
+You can set the initial state with the `defaultOpen` prop.
+
+```tsx
+
+ Open
+
+ Demo dialog
+ Close
+
+
+```
+
+Doing so ensures that the accessibity attributes are set correctly so that the trigger button is approriately announced by assistive technologies.
+
+If you need to control the visibility programmatically from the outside, use the `value` prop.
+You can still use the `AlertDialog.Trigger` and `AlertDialog.Close` components (though it's not necessary), but you need to make sure to create a handler for the `onOpenChange` event and update the state manually.
+
+```tsx
+const [open, setOpen] = React.useState(false);
+
+return (
+
+ Open
+
+ Demo dialog
+ Close
+
+
+);
+```
+
+## Nested dialogs
+
+An alert dialog can open another dialog (or alert dialog).
+At times, it may be useful to know how may open sub-dialogs a given alert dialog has.
+One example of this could be styling the bottom dialog in a way they appear below the top-most one.
+
+The number of open child dialogs is present in the `data-nested-dialogs` attribute and in the `--nested-dialogs` CSS variable on the `` component.
+
+{{"demo": "NestedAlertDialogs.js"}}
+
+Note that when dialogs are nested, only the bottom-most backdrop is rendered.
+
+## Animation
+
+The `` and `` components support transitions on entry and exit.
+
+CSS animations and transitions are supported out of the box.
+If a component has a transition or animation applied to it when it closes, it will be unmounted only after the animation finishes.
+
+As this detection of exit animations requires an extra render, you may opt out of it by setting the `animated` prop on Popup and Backdrop to `false`.
+We also recommend doing so in automated tests, to avoid asynchronous behavior and make testing easier.
+
+Alternatively, you can use JavaScript-based animations with a library like framer-motion, React Spring, or similar.
+With this approach set the `keepMounted` to `true` and let the animation library control mounting and unmounting.
+
+### CSS transitions
+
+Here is an example of how to apply a symmetric scale and fade transition with the default conditionally-rendered behavior:
+
+```jsx
+Alert
+```
+
+```css
+.AlertDialogPopup {
+ transition-property: opacity, transform;
+ transition-duration: 0.2s;
+ /* Represents the final styles once exited */
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+}
+
+/* Represents the final styles once entered */
+.AlertDialogPopup[data-state='open'] {
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(1);
+}
+
+/* Represents the initial styles when entering */
+.AlertDialogPopup[data-entering] {
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+}
+```
+
+Styles need to be applied in three states:
+
+- The exiting styles, placed on the base element class
+- The open styles, placed on the base element class with `[data-state="open"]`
+- The entering styles, placed on the base element class with `[data-entering]`
+
+{{"demo": "AlertDialogWithTransitions.js"}}
+
+In newer browsers, there is a feature called `@starting-style` which allows transitions to occur on open for conditionally-mounted components:
+
+```css
+/* Base UI API - Polyfill */
+.AlertDialogPopup[data-entering] {
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+}
+
+/* Official Browser API - no Firefox support as of May 2024 */
+@starting-style {
+ .AlertDialogPopup[data-state='open'] {
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+ }
+}
+```
+
+### CSS animations
+
+CSS animations can also be used, requiring only two separate declarations:
+
+```css
+@keyframes scale-in {
+ from {
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+ }
+}
+
+@keyframes scale-out {
+ to {
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+ }
+}
+
+.AlertDialogPopup {
+ animation: scale-in 0.2s forwards;
+}
+
+.AlertDialogPopup[data-exiting] {
+ animation: scale-out 0.2s forwards;
+}
+```
+
+### JavaScript animations
+
+The `keepMounted` prop lets an external library control the mounting, for example `framer-motion`'s `AnimatePresence` component.
+
+```js
+function App() {
+ const [open, setOpen] = useState(false);
+ return (
+
+ Trigger
+
+ {open && (
+
+ }
+ >
+ Alert Dialog
+
+ )}
+
+
+ );
+}
+```
+
+### Animation states
+
+Four states are available as data attributes to animate the dialog, which enables full control depending on whether the popup is being animated with CSS transitions or animations, JavaScript, or is using the `keepMounted` prop.
+
+- `[data-state="open"]` - `open` state is `true`.
+- `[data-state="closed"]` - `open` state is `false`. Can still be mounted to the DOM if closing.
+- `[data-entering]` - the popup was just inserted to the DOM. The attribute is removed 1 animation frame later. Enables "starting styles" upon insertion for conditional rendering.
+- `[data-exiting]` - the popup is in the process of being removed from the DOM, but is still mounted.
+
+## Composing a custom React component
+
+Use the `render` prop to override the rendered element:
+
+```jsx
+ } />
+// or
+ } />
+```
+
+## Accessibility
+
+Using the `` sets the required accessibility attributes on the trigger button.
+If you prefer controlling the open state differently, you need to apply these attributes on your own:
+
+```tsx
+const [open, setOpen] = React.useState(false);
+
+return (
+
+
setOpen(true)}
+ >
+ Open
+
+
+
+
+
+
+);
+```
diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.js b/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.js
deleted file mode 100644
index 34965307b9..0000000000
--- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.js
+++ /dev/null
@@ -1,464 +0,0 @@
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import { useAutocomplete } from '@base_ui/react/useAutocomplete';
-import { Button } from '@base_ui/react/Button';
-import { Input } from '@base_ui/react/Input';
-import { Popper } from '@base_ui/react/Popper';
-import { useTheme } from '@mui/system';
-import { unstable_useForkRef as useForkRef } from '@mui/utils';
-import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
-import ClearIcon from '@mui/icons-material/Clear';
-import clsx from 'clsx';
-
-const Autocomplete = React.forwardRef(function Autocomplete(props, ref) {
- const {
- disableClearable = false,
- disabled = false,
- readOnly = false,
- options,
- ...other
- } = props;
-
- const {
- getRootProps,
- getInputProps,
- getPopupIndicatorProps,
- getClearProps,
- getListboxProps,
- getOptionProps,
- dirty,
- id,
- popupOpen,
- focused,
- anchorEl,
- setAnchorEl,
- groupedOptions,
- } = useAutocomplete({
- ...props,
- componentName: 'BaseAutocompleteIntroduction',
- });
-
- const hasClearIcon = !disableClearable && !disabled && dirty && !readOnly;
-
- const rootRef = useForkRef(ref, setAnchorEl);
-
- return (
-
-
-
- {hasClearIcon && (
-
-
-
- )}
-
-
-
-
-
- {anchorEl ? (
-
-
- {groupedOptions.map((option, index) => {
- const optionProps = getOptionProps({ option, index });
-
- return (
-
- {option.label}
-
- );
- })}
-
- {groupedOptions.length === 0 && (
- No results
- )}
-
-
- ) : null}
-
-
- );
-});
-
-Autocomplete.propTypes = {
- /**
- * If `true`, the input can't be cleared.
- * @default false
- */
- disableClearable: PropTypes.oneOf([false]),
- /**
- * If `true`, the component is disabled.
- * @default false
- */
- disabled: PropTypes.bool,
- /**
- * Array of options.
- */
- options: PropTypes.arrayOf(
- PropTypes.shape({
- label: PropTypes.string.isRequired,
- year: PropTypes.number.isRequired,
- }),
- ).isRequired,
- /**
- * If `true`, the component becomes readonly. It is also supported for multiple tags where the tag cannot be deleted.
- * @default false
- */
- readOnly: PropTypes.bool,
-};
-
-export default function AutocompleteIntroduction() {
- return ;
-}
-
-const cyan = {
- 50: '#E9F8FC',
- 100: '#BDEBF4',
- 200: '#99D8E5',
- 300: '#66BACC',
- 400: '#1F94AD',
- 500: '#0D5463',
- 600: '#094855',
- 700: '#063C47',
- 800: '#043039',
- 900: '#022127',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-function Styles() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
- );
-}
-
-const top100Films = [
- { label: 'The Shawshank Redemption', year: 1994 },
- { label: 'The Godfather', year: 1972 },
- { label: 'The Godfather: Part II', year: 1974 },
- { label: 'The Dark Knight', year: 2008 },
- { label: '12 Angry Men', year: 1957 },
- { label: "Schindler's List", year: 1993 },
- { label: 'Pulp Fiction', year: 1994 },
- {
- label: 'The Lord of the Rings: The Return of the King',
- year: 2003,
- },
- { label: 'The Good, the Bad and the Ugly', year: 1966 },
- { label: 'Fight Club', year: 1999 },
- {
- label: 'The Lord of the Rings: The Fellowship of the Ring',
- year: 2001,
- },
- {
- label: 'Star Wars: Episode V - The Empire Strikes Back',
- year: 1980,
- },
- { label: 'Forrest Gump', year: 1994 },
- { label: 'Inception', year: 2010 },
- {
- label: 'The Lord of the Rings: The Two Towers',
- year: 2002,
- },
- { label: "One Flew Over the Cuckoo's Nest", year: 1975 },
- { label: 'Goodfellas', year: 1990 },
- { label: 'The Matrix', year: 1999 },
- { label: 'Seven Samurai', year: 1954 },
- {
- label: 'Star Wars: Episode IV - A New Hope',
- year: 1977,
- },
- { label: 'City of God', year: 2002 },
- { label: 'Se7en', year: 1995 },
- { label: 'The Silence of the Lambs', year: 1991 },
- { label: "It's a Wonderful Life", year: 1946 },
- { label: 'Life Is Beautiful', year: 1997 },
- { label: 'The Usual Suspects', year: 1995 },
- { label: 'Léon: The Professional', year: 1994 },
- { label: 'Spirited Away', year: 2001 },
- { label: 'Saving Private Ryan', year: 1998 },
- { label: 'Once Upon a Time in the West', year: 1968 },
- { label: 'American History X', year: 1998 },
- { label: 'Interstellar', year: 2014 },
- { label: 'Casablanca', year: 1942 },
- { label: 'City Lights', year: 1931 },
- { label: 'Psycho', year: 1960 },
- { label: 'The Green Mile', year: 1999 },
- { label: 'The Intouchables', year: 2011 },
- { label: 'Modern Times', year: 1936 },
- { label: 'Raiders of the Lost Ark', year: 1981 },
- { label: 'Rear Window', year: 1954 },
- { label: 'The Pianist', year: 2002 },
- { label: 'The Departed', year: 2006 },
- { label: 'Terminator 2: Judgment Day', year: 1991 },
- { label: 'Back to the Future', year: 1985 },
- { label: 'Whiplash', year: 2014 },
- { label: 'Gladiator', year: 2000 },
- { label: 'Memento', year: 2000 },
- { label: 'The Prestige', year: 2006 },
- { label: 'The Lion King', year: 1994 },
- { label: 'Apocalypse Now', year: 1979 },
- { label: 'Alien', year: 1979 },
- { label: 'Sunset Boulevard', year: 1950 },
- {
- label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb',
- year: 1964,
- },
- { label: 'The Great Dictator', year: 1940 },
- { label: 'Cinema Paradiso', year: 1988 },
- { label: 'The Lives of Others', year: 2006 },
- { label: 'Grave of the Fireflies', year: 1988 },
- { label: 'Paths of Glory', year: 1957 },
- { label: 'Django Unchained', year: 2012 },
- { label: 'The Shining', year: 1980 },
- { label: 'WALL·E', year: 2008 },
- { label: 'American Beauty', year: 1999 },
- { label: 'The Dark Knight Rises', year: 2012 },
- { label: 'Princess Mononoke', year: 1997 },
- { label: 'Aliens', year: 1986 },
- { label: 'Oldboy', year: 2003 },
- { label: 'Once Upon a Time in America', year: 1984 },
- { label: 'Witness for the Prosecution', year: 1957 },
- { label: 'Das Boot', year: 1981 },
- { label: 'Citizen Kane', year: 1941 },
- { label: 'North by Northwest', year: 1959 },
- { label: 'Vertigo', year: 1958 },
- {
- label: 'Star Wars: Episode VI - Return of the Jedi',
- year: 1983,
- },
- { label: 'Reservoir Dogs', year: 1992 },
- { label: 'Braveheart', year: 1995 },
- { label: 'M', year: 1931 },
- { label: 'Requiem for a Dream', year: 2000 },
- { label: 'Amélie', year: 2001 },
- { label: 'A Clockwork Orange', year: 1971 },
- { label: 'Like Stars on Earth', year: 2007 },
- { label: 'Taxi Driver', year: 1976 },
- { label: 'Lawrence of Arabia', year: 1962 },
- { label: 'Double Indemnity', year: 1944 },
- {
- label: 'Eternal Sunshine of the Spotless Mind',
- year: 2004,
- },
- { label: 'Amadeus', year: 1984 },
- { label: 'To Kill a Mockingbird', year: 1962 },
- { label: 'Toy Story 3', year: 2010 },
- { label: 'Logan', year: 2017 },
- { label: 'Full Metal Jacket', year: 1987 },
- { label: 'Dangal', year: 2016 },
- { label: 'The Sting', year: 1973 },
- { label: '2001: A Space Odyssey', year: 1968 },
- { label: "Singin' in the Rain", year: 1952 },
- { label: 'Toy Story', year: 1995 },
- { label: 'Bicycle Thieves', year: 1948 },
- { label: 'The Kid', year: 1921 },
- { label: 'Inglourious Basterds', year: 2009 },
- { label: 'Snatch', year: 2000 },
- { label: '3 Idiots', year: 2009 },
- { label: 'Monty Python and the Holy Grail', year: 1975 },
-];
diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.tsx b/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.tsx
deleted file mode 100644
index 2399689329..0000000000
--- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.tsx
+++ /dev/null
@@ -1,441 +0,0 @@
-import * as React from 'react';
-import {
- useAutocomplete,
- UseAutocompleteProps,
-} from '@base_ui/react/useAutocomplete';
-import { Button } from '@base_ui/react/Button';
-import { Input } from '@base_ui/react/Input';
-import { Popper } from '@base_ui/react/Popper';
-import { useTheme } from '@mui/system';
-import { unstable_useForkRef as useForkRef } from '@mui/utils';
-import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
-import ClearIcon from '@mui/icons-material/Clear';
-import clsx from 'clsx';
-
-const Autocomplete = React.forwardRef(function Autocomplete(
- props: UseAutocompleteProps<(typeof top100Films)[number], false, false, false>,
- ref: React.ForwardedRef,
-) {
- const {
- disableClearable = false,
- disabled = false,
- readOnly = false,
- options,
- ...other
- } = props;
-
- const {
- getRootProps,
- getInputProps,
- getPopupIndicatorProps,
- getClearProps,
- getListboxProps,
- getOptionProps,
- dirty,
- id,
- popupOpen,
- focused,
- anchorEl,
- setAnchorEl,
- groupedOptions,
- } = useAutocomplete({
- ...props,
- componentName: 'BaseAutocompleteIntroduction',
- });
-
- const hasClearIcon = !disableClearable && !disabled && dirty && !readOnly;
-
- const rootRef = useForkRef(ref, setAnchorEl);
-
- return (
-
-
-
- {hasClearIcon && (
-
-
-
- )}
-
-
-
-
- {anchorEl ? (
-
-
- {(groupedOptions as typeof top100Films).map((option, index) => {
- const optionProps = getOptionProps({ option, index });
-
- return (
-
- {option.label}
-
- );
- })}
-
- {groupedOptions.length === 0 && (
- No results
- )}
-
-
- ) : null}
-
-
- );
-});
-
-export default function AutocompleteIntroduction() {
- return ;
-}
-
-const cyan = {
- 50: '#E9F8FC',
- 100: '#BDEBF4',
- 200: '#99D8E5',
- 300: '#66BACC',
- 400: '#1F94AD',
- 500: '#0D5463',
- 600: '#094855',
- 700: '#063C47',
- 800: '#043039',
- 900: '#022127',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-function Styles() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
- );
-}
-
-const top100Films = [
- { label: 'The Shawshank Redemption', year: 1994 },
- { label: 'The Godfather', year: 1972 },
- { label: 'The Godfather: Part II', year: 1974 },
- { label: 'The Dark Knight', year: 2008 },
- { label: '12 Angry Men', year: 1957 },
- { label: "Schindler's List", year: 1993 },
- { label: 'Pulp Fiction', year: 1994 },
- {
- label: 'The Lord of the Rings: The Return of the King',
- year: 2003,
- },
- { label: 'The Good, the Bad and the Ugly', year: 1966 },
- { label: 'Fight Club', year: 1999 },
- {
- label: 'The Lord of the Rings: The Fellowship of the Ring',
- year: 2001,
- },
- {
- label: 'Star Wars: Episode V - The Empire Strikes Back',
- year: 1980,
- },
- { label: 'Forrest Gump', year: 1994 },
- { label: 'Inception', year: 2010 },
- {
- label: 'The Lord of the Rings: The Two Towers',
- year: 2002,
- },
- { label: "One Flew Over the Cuckoo's Nest", year: 1975 },
- { label: 'Goodfellas', year: 1990 },
- { label: 'The Matrix', year: 1999 },
- { label: 'Seven Samurai', year: 1954 },
- {
- label: 'Star Wars: Episode IV - A New Hope',
- year: 1977,
- },
- { label: 'City of God', year: 2002 },
- { label: 'Se7en', year: 1995 },
- { label: 'The Silence of the Lambs', year: 1991 },
- { label: "It's a Wonderful Life", year: 1946 },
- { label: 'Life Is Beautiful', year: 1997 },
- { label: 'The Usual Suspects', year: 1995 },
- { label: 'Léon: The Professional', year: 1994 },
- { label: 'Spirited Away', year: 2001 },
- { label: 'Saving Private Ryan', year: 1998 },
- { label: 'Once Upon a Time in the West', year: 1968 },
- { label: 'American History X', year: 1998 },
- { label: 'Interstellar', year: 2014 },
- { label: 'Casablanca', year: 1942 },
- { label: 'City Lights', year: 1931 },
- { label: 'Psycho', year: 1960 },
- { label: 'The Green Mile', year: 1999 },
- { label: 'The Intouchables', year: 2011 },
- { label: 'Modern Times', year: 1936 },
- { label: 'Raiders of the Lost Ark', year: 1981 },
- { label: 'Rear Window', year: 1954 },
- { label: 'The Pianist', year: 2002 },
- { label: 'The Departed', year: 2006 },
- { label: 'Terminator 2: Judgment Day', year: 1991 },
- { label: 'Back to the Future', year: 1985 },
- { label: 'Whiplash', year: 2014 },
- { label: 'Gladiator', year: 2000 },
- { label: 'Memento', year: 2000 },
- { label: 'The Prestige', year: 2006 },
- { label: 'The Lion King', year: 1994 },
- { label: 'Apocalypse Now', year: 1979 },
- { label: 'Alien', year: 1979 },
- { label: 'Sunset Boulevard', year: 1950 },
- {
- label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb',
- year: 1964,
- },
- { label: 'The Great Dictator', year: 1940 },
- { label: 'Cinema Paradiso', year: 1988 },
- { label: 'The Lives of Others', year: 2006 },
- { label: 'Grave of the Fireflies', year: 1988 },
- { label: 'Paths of Glory', year: 1957 },
- { label: 'Django Unchained', year: 2012 },
- { label: 'The Shining', year: 1980 },
- { label: 'WALL·E', year: 2008 },
- { label: 'American Beauty', year: 1999 },
- { label: 'The Dark Knight Rises', year: 2012 },
- { label: 'Princess Mononoke', year: 1997 },
- { label: 'Aliens', year: 1986 },
- { label: 'Oldboy', year: 2003 },
- { label: 'Once Upon a Time in America', year: 1984 },
- { label: 'Witness for the Prosecution', year: 1957 },
- { label: 'Das Boot', year: 1981 },
- { label: 'Citizen Kane', year: 1941 },
- { label: 'North by Northwest', year: 1959 },
- { label: 'Vertigo', year: 1958 },
- {
- label: 'Star Wars: Episode VI - Return of the Jedi',
- year: 1983,
- },
- { label: 'Reservoir Dogs', year: 1992 },
- { label: 'Braveheart', year: 1995 },
- { label: 'M', year: 1931 },
- { label: 'Requiem for a Dream', year: 2000 },
- { label: 'Amélie', year: 2001 },
- { label: 'A Clockwork Orange', year: 1971 },
- { label: 'Like Stars on Earth', year: 2007 },
- { label: 'Taxi Driver', year: 1976 },
- { label: 'Lawrence of Arabia', year: 1962 },
- { label: 'Double Indemnity', year: 1944 },
- {
- label: 'Eternal Sunshine of the Spotless Mind',
- year: 2004,
- },
- { label: 'Amadeus', year: 1984 },
- { label: 'To Kill a Mockingbird', year: 1962 },
- { label: 'Toy Story 3', year: 2010 },
- { label: 'Logan', year: 2017 },
- { label: 'Full Metal Jacket', year: 1987 },
- { label: 'Dangal', year: 2016 },
- { label: 'The Sting', year: 1973 },
- { label: '2001: A Space Odyssey', year: 1968 },
- { label: "Singin' in the Rain", year: 1952 },
- { label: 'Toy Story', year: 1995 },
- { label: 'Bicycle Thieves', year: 1948 },
- { label: 'The Kid', year: 1921 },
- { label: 'Inglourious Basterds', year: 2009 },
- { label: 'Snatch', year: 2000 },
- { label: '3 Idiots', year: 2009 },
- { label: 'Monty Python and the Holy Grail', year: 1975 },
-];
diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.tsx.preview b/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.tsx.preview
deleted file mode 100644
index 0991a681b0..0000000000
--- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.tsx.preview
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.js b/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.js
deleted file mode 100644
index 3a8d047c21..0000000000
--- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.js
+++ /dev/null
@@ -1,435 +0,0 @@
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import { useAutocomplete } from '@base_ui/react/useAutocomplete';
-import { Button } from '@base_ui/react/Button';
-import { Popper } from '@base_ui/react/Popper';
-import { styled } from '@mui/system';
-import { unstable_useForkRef as useForkRef } from '@mui/utils';
-import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
-import ClearIcon from '@mui/icons-material/Clear';
-
-const Autocomplete = React.forwardRef(function Autocomplete(props, ref) {
- const {
- disableClearable = false,
- disabled = false,
- readOnly = false,
- ...other
- } = props;
-
- const {
- getRootProps,
- getInputProps,
- getPopupIndicatorProps,
- getClearProps,
- getListboxProps,
- getOptionProps,
- dirty,
- id,
- popupOpen,
- focused,
- anchorEl,
- setAnchorEl,
- groupedOptions,
- } = useAutocomplete({
- ...props,
- componentName: 'BaseAutocompleteIntroduction',
- });
-
- const hasClearIcon = !disableClearable && !disabled && dirty && !readOnly;
-
- const rootRef = useForkRef(ref, setAnchorEl);
-
- return (
-
-
-
- {hasClearIcon && (
-
-
-
- )}
-
-
-
-
-
- {anchorEl ? (
-
-
- {groupedOptions.map((option, index) => {
- const optionProps = getOptionProps({ option, index });
-
- return {option.label} ;
- })}
-
- {groupedOptions.length === 0 && (
- No results
- )}
-
-
- ) : null}
-
- );
-});
-
-Autocomplete.propTypes = {
- /**
- * If `true`, the input can't be cleared.
- * @default false
- */
- disableClearable: PropTypes.oneOf([false]),
- /**
- * If `true`, the component is disabled.
- * @default false
- */
- disabled: PropTypes.bool,
- /**
- * If `true`, the component becomes readonly. It is also supported for multiple tags where the tag cannot be deleted.
- * @default false
- */
- readOnly: PropTypes.bool,
-};
-
-export default function AutocompleteIntroduction() {
- return ;
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#99CCF3',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
- 900: '#003A75',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const StyledAutocompleteRoot = styled('div')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 400;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
- display: flex;
- gap: 5px;
- padding-right: 5px;
- overflow: hidden;
- width: 320px;
-
- &.focused {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]};
- }
-
- &:hover {
- background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]};
- border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]};
- }
-
- &:focus-visible {
- outline: 0;
- }
-`,
-);
-
-const StyledInput = styled('input')(
- ({ theme }) => `
- font-size: 0.875rem;
- font-family: inherit;
- font-weight: 400;
- line-height: 1.5;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: inherit;
- border: none;
- border-radius: inherit;
- padding: 8px 12px;
- outline: 0;
- flex: 1 0 auto;
-`,
-);
-
-// ComponentPageTabs has z-index: 1000
-const StyledPopper = styled('div')`
- position: relative;
- z-index: 1001;
- width: 320px;
-`;
-
-const StyledListbox = styled('ul')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- box-sizing: border-box;
- padding: 6px;
- margin: 12px 0;
- min-width: 320px;
- border-radius: 12px;
- overflow: auto;
- outline: 0px;
- max-height: 300px;
- z-index: 1;
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- box-shadow: 0px 4px 6px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.3)' : 'rgba(0,0,0, 0.05)'
- };
- `,
-);
-
-const StyledOption = styled('li')(
- ({ theme }) => `
- list-style: none;
- padding: 8px;
- border-radius: 8px;
- cursor: default;
-
- &:last-of-type {
- border-bottom: none;
- }
-
- &:hover {
- cursor: pointer;
- }
-
- &[aria-selected=true] {
- background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
- color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
- }
-
- &.Mui-focused,
- &.Mui-focusVisible {
- background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- }
-
- &.Mui-focusVisible {
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]};
- }
-
- &[aria-selected=true].Mui-focused,
- &[aria-selected=true].Mui-focusVisible {
- background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
- color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
- }
- `,
-);
-
-const StyledPopupIndicator = styled(Button)(
- ({ theme }) => `
- outline: 0;
- box-shadow: none;
- border: 0;
- border-radius: 4px;
- background-color: transparent;
- align-self: center;
- padding: 0 2px;
-
- &:hover {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : blue[100]};
- cursor: pointer;
- }
-
- & > svg {
- transform: translateY(2px);
- }
-
- &.popupOpen > svg {
- transform: translateY(2px) rotate(180deg);
- }
- `,
-);
-
-const StyledClearIndicator = styled(Button)(
- ({ theme }) => `
- outline: 0;
- box-shadow: none;
- border: 0;
- border-radius: 4px;
- background-color: transparent;
- align-self: center;
- padding: 0 2px;
-
- &:hover {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : blue[100]};
- cursor: pointer;
- }
-
- & > svg {
- transform: translateY(2px) scale(0.9);
- }
- `,
-);
-
-const StyledNoOptions = styled('li')`
- list-style: none;
- padding: 8px;
- cursor: default;
-`;
-
-const top100Films = [
- { label: 'The Shawshank Redemption', year: 1994 },
- { label: 'The Godfather', year: 1972 },
- { label: 'The Godfather: Part II', year: 1974 },
- { label: 'The Dark Knight', year: 2008 },
- { label: '12 Angry Men', year: 1957 },
- { label: "Schindler's List", year: 1993 },
- { label: 'Pulp Fiction', year: 1994 },
- {
- label: 'The Lord of the Rings: The Return of the King',
- year: 2003,
- },
- { label: 'The Good, the Bad and the Ugly', year: 1966 },
- { label: 'Fight Club', year: 1999 },
- {
- label: 'The Lord of the Rings: The Fellowship of the Ring',
- year: 2001,
- },
- {
- label: 'Star Wars: Episode V - The Empire Strikes Back',
- year: 1980,
- },
- { label: 'Forrest Gump', year: 1994 },
- { label: 'Inception', year: 2010 },
- {
- label: 'The Lord of the Rings: The Two Towers',
- year: 2002,
- },
- { label: "One Flew Over the Cuckoo's Nest", year: 1975 },
- { label: 'Goodfellas', year: 1990 },
- { label: 'The Matrix', year: 1999 },
- { label: 'Seven Samurai', year: 1954 },
- {
- label: 'Star Wars: Episode IV - A New Hope',
- year: 1977,
- },
- { label: 'City of God', year: 2002 },
- { label: 'Se7en', year: 1995 },
- { label: 'The Silence of the Lambs', year: 1991 },
- { label: "It's a Wonderful Life", year: 1946 },
- { label: 'Life Is Beautiful', year: 1997 },
- { label: 'The Usual Suspects', year: 1995 },
- { label: 'Léon: The Professional', year: 1994 },
- { label: 'Spirited Away', year: 2001 },
- { label: 'Saving Private Ryan', year: 1998 },
- { label: 'Once Upon a Time in the West', year: 1968 },
- { label: 'American History X', year: 1998 },
- { label: 'Interstellar', year: 2014 },
- { label: 'Casablanca', year: 1942 },
- { label: 'City Lights', year: 1931 },
- { label: 'Psycho', year: 1960 },
- { label: 'The Green Mile', year: 1999 },
- { label: 'The Intouchables', year: 2011 },
- { label: 'Modern Times', year: 1936 },
- { label: 'Raiders of the Lost Ark', year: 1981 },
- { label: 'Rear Window', year: 1954 },
- { label: 'The Pianist', year: 2002 },
- { label: 'The Departed', year: 2006 },
- { label: 'Terminator 2: Judgment Day', year: 1991 },
- { label: 'Back to the Future', year: 1985 },
- { label: 'Whiplash', year: 2014 },
- { label: 'Gladiator', year: 2000 },
- { label: 'Memento', year: 2000 },
- { label: 'The Prestige', year: 2006 },
- { label: 'The Lion King', year: 1994 },
- { label: 'Apocalypse Now', year: 1979 },
- { label: 'Alien', year: 1979 },
- { label: 'Sunset Boulevard', year: 1950 },
- {
- label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb',
- year: 1964,
- },
- { label: 'The Great Dictator', year: 1940 },
- { label: 'Cinema Paradiso', year: 1988 },
- { label: 'The Lives of Others', year: 2006 },
- { label: 'Grave of the Fireflies', year: 1988 },
- { label: 'Paths of Glory', year: 1957 },
- { label: 'Django Unchained', year: 2012 },
- { label: 'The Shining', year: 1980 },
- { label: 'WALL·E', year: 2008 },
- { label: 'American Beauty', year: 1999 },
- { label: 'The Dark Knight Rises', year: 2012 },
- { label: 'Princess Mononoke', year: 1997 },
- { label: 'Aliens', year: 1986 },
- { label: 'Oldboy', year: 2003 },
- { label: 'Once Upon a Time in America', year: 1984 },
- { label: 'Witness for the Prosecution', year: 1957 },
- { label: 'Das Boot', year: 1981 },
- { label: 'Citizen Kane', year: 1941 },
- { label: 'North by Northwest', year: 1959 },
- { label: 'Vertigo', year: 1958 },
- {
- label: 'Star Wars: Episode VI - Return of the Jedi',
- year: 1983,
- },
- { label: 'Reservoir Dogs', year: 1992 },
- { label: 'Braveheart', year: 1995 },
- { label: 'M', year: 1931 },
- { label: 'Requiem for a Dream', year: 2000 },
- { label: 'Amélie', year: 2001 },
- { label: 'A Clockwork Orange', year: 1971 },
- { label: 'Like Stars on Earth', year: 2007 },
- { label: 'Taxi Driver', year: 1976 },
- { label: 'Lawrence of Arabia', year: 1962 },
- { label: 'Double Indemnity', year: 1944 },
- {
- label: 'Eternal Sunshine of the Spotless Mind',
- year: 2004,
- },
- { label: 'Amadeus', year: 1984 },
- { label: 'To Kill a Mockingbird', year: 1962 },
- { label: 'Toy Story 3', year: 2010 },
- { label: 'Logan', year: 2017 },
- { label: 'Full Metal Jacket', year: 1987 },
- { label: 'Dangal', year: 2016 },
- { label: 'The Sting', year: 1973 },
- { label: '2001: A Space Odyssey', year: 1968 },
- { label: "Singin' in the Rain", year: 1952 },
- { label: 'Toy Story', year: 1995 },
- { label: 'Bicycle Thieves', year: 1948 },
- { label: 'The Kid', year: 1921 },
- { label: 'Inglourious Basterds', year: 2009 },
- { label: 'Snatch', year: 2000 },
- { label: '3 Idiots', year: 2009 },
- { label: 'Monty Python and the Holy Grail', year: 1975 },
-];
diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.tsx b/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.tsx
deleted file mode 100644
index 00d8c19d76..0000000000
--- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.tsx
+++ /dev/null
@@ -1,421 +0,0 @@
-import * as React from 'react';
-import {
- useAutocomplete,
- UseAutocompleteProps,
-} from '@base_ui/react/useAutocomplete';
-import { Button } from '@base_ui/react/Button';
-import { Popper } from '@base_ui/react/Popper';
-import { styled } from '@mui/system';
-import { unstable_useForkRef as useForkRef } from '@mui/utils';
-import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
-import ClearIcon from '@mui/icons-material/Clear';
-
-const Autocomplete = React.forwardRef(function Autocomplete(
- props: UseAutocompleteProps<(typeof top100Films)[number], false, false, false>,
- ref: React.ForwardedRef,
-) {
- const {
- disableClearable = false,
- disabled = false,
- readOnly = false,
- ...other
- } = props;
-
- const {
- getRootProps,
- getInputProps,
- getPopupIndicatorProps,
- getClearProps,
- getListboxProps,
- getOptionProps,
- dirty,
- id,
- popupOpen,
- focused,
- anchorEl,
- setAnchorEl,
- groupedOptions,
- } = useAutocomplete({
- ...props,
- componentName: 'BaseAutocompleteIntroduction',
- });
-
- const hasClearIcon = !disableClearable && !disabled && dirty && !readOnly;
-
- const rootRef = useForkRef(ref, setAnchorEl);
-
- return (
-
-
-
- {hasClearIcon && (
-
-
-
- )}
-
-
-
-
- {anchorEl ? (
-
-
- {(groupedOptions as typeof top100Films).map((option, index) => {
- const optionProps = getOptionProps({ option, index });
-
- return {option.label} ;
- })}
-
- {groupedOptions.length === 0 && (
- No results
- )}
-
-
- ) : null}
-
- );
-});
-
-export default function AutocompleteIntroduction() {
- return ;
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#99CCF3',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
- 900: '#003A75',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const StyledAutocompleteRoot = styled('div')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 400;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
- display: flex;
- gap: 5px;
- padding-right: 5px;
- overflow: hidden;
- width: 320px;
-
- &.focused {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]};
- }
-
- &:hover {
- background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]};
- border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]};
- }
-
- &:focus-visible {
- outline: 0;
- }
-`,
-);
-
-const StyledInput = styled('input')(
- ({ theme }) => `
- font-size: 0.875rem;
- font-family: inherit;
- font-weight: 400;
- line-height: 1.5;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: inherit;
- border: none;
- border-radius: inherit;
- padding: 8px 12px;
- outline: 0;
- flex: 1 0 auto;
-`,
-);
-
-// ComponentPageTabs has z-index: 1000
-const StyledPopper = styled('div')`
- position: relative;
- z-index: 1001;
- width: 320px;
-`;
-
-const StyledListbox = styled('ul')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- box-sizing: border-box;
- padding: 6px;
- margin: 12px 0;
- min-width: 320px;
- border-radius: 12px;
- overflow: auto;
- outline: 0px;
- max-height: 300px;
- z-index: 1;
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- box-shadow: 0px 4px 6px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.3)' : 'rgba(0,0,0, 0.05)'
- };
- `,
-);
-
-const StyledOption = styled('li')(
- ({ theme }) => `
- list-style: none;
- padding: 8px;
- border-radius: 8px;
- cursor: default;
-
- &:last-of-type {
- border-bottom: none;
- }
-
- &:hover {
- cursor: pointer;
- }
-
- &[aria-selected=true] {
- background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
- color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
- }
-
- &.Mui-focused,
- &.Mui-focusVisible {
- background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- }
-
- &.Mui-focusVisible {
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]};
- }
-
- &[aria-selected=true].Mui-focused,
- &[aria-selected=true].Mui-focusVisible {
- background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
- color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
- }
- `,
-);
-
-const StyledPopupIndicator = styled(Button)(
- ({ theme }) => `
- outline: 0;
- box-shadow: none;
- border: 0;
- border-radius: 4px;
- background-color: transparent;
- align-self: center;
- padding: 0 2px;
-
- &:hover {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : blue[100]};
- cursor: pointer;
- }
-
- & > svg {
- transform: translateY(2px);
- }
-
- &.popupOpen > svg {
- transform: translateY(2px) rotate(180deg);
- }
- `,
-);
-
-const StyledClearIndicator = styled(Button)(
- ({ theme }) => `
- outline: 0;
- box-shadow: none;
- border: 0;
- border-radius: 4px;
- background-color: transparent;
- align-self: center;
- padding: 0 2px;
-
- &:hover {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : blue[100]};
- cursor: pointer;
- }
-
- & > svg {
- transform: translateY(2px) scale(0.9);
- }
- `,
-);
-
-const StyledNoOptions = styled('li')`
- list-style: none;
- padding: 8px;
- cursor: default;
-`;
-
-const top100Films = [
- { label: 'The Shawshank Redemption', year: 1994 },
- { label: 'The Godfather', year: 1972 },
- { label: 'The Godfather: Part II', year: 1974 },
- { label: 'The Dark Knight', year: 2008 },
- { label: '12 Angry Men', year: 1957 },
- { label: "Schindler's List", year: 1993 },
- { label: 'Pulp Fiction', year: 1994 },
- {
- label: 'The Lord of the Rings: The Return of the King',
- year: 2003,
- },
- { label: 'The Good, the Bad and the Ugly', year: 1966 },
- { label: 'Fight Club', year: 1999 },
- {
- label: 'The Lord of the Rings: The Fellowship of the Ring',
- year: 2001,
- },
- {
- label: 'Star Wars: Episode V - The Empire Strikes Back',
- year: 1980,
- },
- { label: 'Forrest Gump', year: 1994 },
- { label: 'Inception', year: 2010 },
- {
- label: 'The Lord of the Rings: The Two Towers',
- year: 2002,
- },
- { label: "One Flew Over the Cuckoo's Nest", year: 1975 },
- { label: 'Goodfellas', year: 1990 },
- { label: 'The Matrix', year: 1999 },
- { label: 'Seven Samurai', year: 1954 },
- {
- label: 'Star Wars: Episode IV - A New Hope',
- year: 1977,
- },
- { label: 'City of God', year: 2002 },
- { label: 'Se7en', year: 1995 },
- { label: 'The Silence of the Lambs', year: 1991 },
- { label: "It's a Wonderful Life", year: 1946 },
- { label: 'Life Is Beautiful', year: 1997 },
- { label: 'The Usual Suspects', year: 1995 },
- { label: 'Léon: The Professional', year: 1994 },
- { label: 'Spirited Away', year: 2001 },
- { label: 'Saving Private Ryan', year: 1998 },
- { label: 'Once Upon a Time in the West', year: 1968 },
- { label: 'American History X', year: 1998 },
- { label: 'Interstellar', year: 2014 },
- { label: 'Casablanca', year: 1942 },
- { label: 'City Lights', year: 1931 },
- { label: 'Psycho', year: 1960 },
- { label: 'The Green Mile', year: 1999 },
- { label: 'The Intouchables', year: 2011 },
- { label: 'Modern Times', year: 1936 },
- { label: 'Raiders of the Lost Ark', year: 1981 },
- { label: 'Rear Window', year: 1954 },
- { label: 'The Pianist', year: 2002 },
- { label: 'The Departed', year: 2006 },
- { label: 'Terminator 2: Judgment Day', year: 1991 },
- { label: 'Back to the Future', year: 1985 },
- { label: 'Whiplash', year: 2014 },
- { label: 'Gladiator', year: 2000 },
- { label: 'Memento', year: 2000 },
- { label: 'The Prestige', year: 2006 },
- { label: 'The Lion King', year: 1994 },
- { label: 'Apocalypse Now', year: 1979 },
- { label: 'Alien', year: 1979 },
- { label: 'Sunset Boulevard', year: 1950 },
- {
- label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb',
- year: 1964,
- },
- { label: 'The Great Dictator', year: 1940 },
- { label: 'Cinema Paradiso', year: 1988 },
- { label: 'The Lives of Others', year: 2006 },
- { label: 'Grave of the Fireflies', year: 1988 },
- { label: 'Paths of Glory', year: 1957 },
- { label: 'Django Unchained', year: 2012 },
- { label: 'The Shining', year: 1980 },
- { label: 'WALL·E', year: 2008 },
- { label: 'American Beauty', year: 1999 },
- { label: 'The Dark Knight Rises', year: 2012 },
- { label: 'Princess Mononoke', year: 1997 },
- { label: 'Aliens', year: 1986 },
- { label: 'Oldboy', year: 2003 },
- { label: 'Once Upon a Time in America', year: 1984 },
- { label: 'Witness for the Prosecution', year: 1957 },
- { label: 'Das Boot', year: 1981 },
- { label: 'Citizen Kane', year: 1941 },
- { label: 'North by Northwest', year: 1959 },
- { label: 'Vertigo', year: 1958 },
- {
- label: 'Star Wars: Episode VI - Return of the Jedi',
- year: 1983,
- },
- { label: 'Reservoir Dogs', year: 1992 },
- { label: 'Braveheart', year: 1995 },
- { label: 'M', year: 1931 },
- { label: 'Requiem for a Dream', year: 2000 },
- { label: 'Amélie', year: 2001 },
- { label: 'A Clockwork Orange', year: 1971 },
- { label: 'Like Stars on Earth', year: 2007 },
- { label: 'Taxi Driver', year: 1976 },
- { label: 'Lawrence of Arabia', year: 1962 },
- { label: 'Double Indemnity', year: 1944 },
- {
- label: 'Eternal Sunshine of the Spotless Mind',
- year: 2004,
- },
- { label: 'Amadeus', year: 1984 },
- { label: 'To Kill a Mockingbird', year: 1962 },
- { label: 'Toy Story 3', year: 2010 },
- { label: 'Logan', year: 2017 },
- { label: 'Full Metal Jacket', year: 1987 },
- { label: 'Dangal', year: 2016 },
- { label: 'The Sting', year: 1973 },
- { label: '2001: A Space Odyssey', year: 1968 },
- { label: "Singin' in the Rain", year: 1952 },
- { label: 'Toy Story', year: 1995 },
- { label: 'Bicycle Thieves', year: 1948 },
- { label: 'The Kid', year: 1921 },
- { label: 'Inglourious Basterds', year: 2009 },
- { label: 'Snatch', year: 2000 },
- { label: '3 Idiots', year: 2009 },
- { label: 'Monty Python and the Holy Grail', year: 1975 },
-];
diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.tsx.preview b/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.tsx.preview
deleted file mode 100644
index 0991a681b0..0000000000
--- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.tsx.preview
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.js b/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.js
deleted file mode 100644
index 1aabb6cffa..0000000000
--- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.js
+++ /dev/null
@@ -1,294 +0,0 @@
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import { useAutocomplete } from '@base_ui/react/useAutocomplete';
-import { Button } from '@base_ui/react/Button';
-import { Popper } from '@base_ui/react/Popper';
-import { unstable_useForkRef as useForkRef } from '@mui/utils';
-import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
-import ClearIcon from '@mui/icons-material/Clear';
-import clsx from 'clsx';
-
-const Autocomplete = React.forwardRef(function Autocomplete(props, ref) {
- const {
- disableClearable = false,
- disabled = false,
- readOnly = false,
- options,
- isOptionEqualToValue,
- ...other
- } = props;
-
- const {
- getRootProps,
- getInputProps,
- getPopupIndicatorProps,
- getClearProps,
- getListboxProps,
- getOptionProps,
- dirty,
- id,
- popupOpen,
- focused,
- anchorEl,
- setAnchorEl,
- groupedOptions,
- } = useAutocomplete({
- ...props,
- componentName: 'BaseAutocompleteIntroduction',
- });
-
- const hasClearIcon = !disableClearable && !disabled && dirty && !readOnly;
-
- const rootRef = useForkRef(ref, setAnchorEl);
-
- return (
-
-
-
- {hasClearIcon && (
-
-
-
- )}
-
-
-
-
-
- {anchorEl && (
-
-
- {groupedOptions.map((option, index) => {
- const optionProps = getOptionProps({ option, index });
-
- return (
-
- {option.label}
-
- );
- })}
-
- {groupedOptions.length === 0 && (
- No results
- )}
-
-
- )}
-
- );
-});
-
-Autocomplete.propTypes = {
- /**
- * If `true`, the input can't be cleared.
- * @default false
- */
- disableClearable: PropTypes.oneOf([false]),
- /**
- * If `true`, the component is disabled.
- * @default false
- */
- disabled: PropTypes.bool,
- /**
- * Used to determine if the option represents the given value.
- * Uses strict equality by default.
- * ⚠️ Both arguments need to be handled, an option can only match with one value.
- *
- * @param {Value} option The option to test.
- * @param {Value} value The value to test against.
- * @returns {boolean}
- */
- isOptionEqualToValue: PropTypes.func,
- /**
- * Array of options.
- */
- options: PropTypes.arrayOf(
- PropTypes.shape({
- label: PropTypes.string.isRequired,
- year: PropTypes.number.isRequired,
- }),
- ).isRequired,
- /**
- * If `true`, the component becomes readonly. It is also supported for multiple tags where the tag cannot be deleted.
- * @default false
- */
- readOnly: PropTypes.bool,
-};
-
-export default function AutocompleteIntroduction() {
- return (
- option.label === value.label}
- />
- );
-}
-
-const top100Films = [
- { label: 'The Shawshank Redemption', year: 1994 },
- { label: 'The Godfather', year: 1972 },
- { label: 'The Godfather: Part II', year: 1974 },
- { label: 'The Dark Knight', year: 2008 },
- { label: '12 Angry Men', year: 1957 },
- { label: "Schindler's List", year: 1993 },
- { label: 'Pulp Fiction', year: 1994 },
- {
- label: 'The Lord of the Rings: The Return of the King',
- year: 2003,
- },
- { label: 'The Good, the Bad and the Ugly', year: 1966 },
- { label: 'Fight Club', year: 1999 },
- {
- label: 'The Lord of the Rings: The Fellowship of the Ring',
- year: 2001,
- },
- {
- label: 'Star Wars: Episode V - The Empire Strikes Back',
- year: 1980,
- },
- { label: 'Forrest Gump', year: 1994 },
- { label: 'Inception', year: 2010 },
- {
- label: 'The Lord of the Rings: The Two Towers',
- year: 2002,
- },
- { label: "One Flew Over the Cuckoo's Nest", year: 1975 },
- { label: 'Goodfellas', year: 1990 },
- { label: 'The Matrix', year: 1999 },
- { label: 'Seven Samurai', year: 1954 },
- {
- label: 'Star Wars: Episode IV - A New Hope',
- year: 1977,
- },
- { label: 'City of God', year: 2002 },
- { label: 'Se7en', year: 1995 },
- { label: 'The Silence of the Lambs', year: 1991 },
- { label: "It's a Wonderful Life", year: 1946 },
- { label: 'Life Is Beautiful', year: 1997 },
- { label: 'The Usual Suspects', year: 1995 },
- { label: 'Léon: The Professional', year: 1994 },
- { label: 'Spirited Away', year: 2001 },
- { label: 'Saving Private Ryan', year: 1998 },
- { label: 'Once Upon a Time in the West', year: 1968 },
- { label: 'American History X', year: 1998 },
- { label: 'Interstellar', year: 2014 },
- { label: 'Casablanca', year: 1942 },
- { label: 'City Lights', year: 1931 },
- { label: 'Psycho', year: 1960 },
- { label: 'The Green Mile', year: 1999 },
- { label: 'The Intouchables', year: 2011 },
- { label: 'Modern Times', year: 1936 },
- { label: 'Raiders of the Lost Ark', year: 1981 },
- { label: 'Rear Window', year: 1954 },
- { label: 'The Pianist', year: 2002 },
- { label: 'The Departed', year: 2006 },
- { label: 'Terminator 2: Judgment Day', year: 1991 },
- { label: 'Back to the Future', year: 1985 },
- { label: 'Whiplash', year: 2014 },
- { label: 'Gladiator', year: 2000 },
- { label: 'Memento', year: 2000 },
- { label: 'The Prestige', year: 2006 },
- { label: 'The Lion King', year: 1994 },
- { label: 'Apocalypse Now', year: 1979 },
- { label: 'Alien', year: 1979 },
- { label: 'Sunset Boulevard', year: 1950 },
- {
- label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb',
- year: 1964,
- },
- { label: 'The Great Dictator', year: 1940 },
- { label: 'Cinema Paradiso', year: 1988 },
- { label: 'The Lives of Others', year: 2006 },
- { label: 'Grave of the Fireflies', year: 1988 },
- { label: 'Paths of Glory', year: 1957 },
- { label: 'Django Unchained', year: 2012 },
- { label: 'The Shining', year: 1980 },
- { label: 'WALL·E', year: 2008 },
- { label: 'American Beauty', year: 1999 },
- { label: 'The Dark Knight Rises', year: 2012 },
- { label: 'Princess Mononoke', year: 1997 },
- { label: 'Aliens', year: 1986 },
- { label: 'Oldboy', year: 2003 },
- { label: 'Once Upon a Time in America', year: 1984 },
- { label: 'Witness for the Prosecution', year: 1957 },
- { label: 'Das Boot', year: 1981 },
- { label: 'Citizen Kane', year: 1941 },
- { label: 'North by Northwest', year: 1959 },
- { label: 'Vertigo', year: 1958 },
- {
- label: 'Star Wars: Episode VI - Return of the Jedi',
- year: 1983,
- },
- { label: 'Reservoir Dogs', year: 1992 },
- { label: 'Braveheart', year: 1995 },
- { label: 'M', year: 1931 },
- { label: 'Requiem for a Dream', year: 2000 },
- { label: 'Amélie', year: 2001 },
- { label: 'A Clockwork Orange', year: 1971 },
- { label: 'Like Stars on Earth', year: 2007 },
- { label: 'Taxi Driver', year: 1976 },
- { label: 'Lawrence of Arabia', year: 1962 },
- { label: 'Double Indemnity', year: 1944 },
- {
- label: 'Eternal Sunshine of the Spotless Mind',
- year: 2004,
- },
- { label: 'Amadeus', year: 1984 },
- { label: 'To Kill a Mockingbird', year: 1962 },
- { label: 'Toy Story 3', year: 2010 },
- { label: 'Logan', year: 2017 },
- { label: 'Full Metal Jacket', year: 1987 },
- { label: 'Dangal', year: 2016 },
- { label: 'The Sting', year: 1973 },
- { label: '2001: A Space Odyssey', year: 1968 },
- { label: "Singin' in the Rain", year: 1952 },
- { label: 'Toy Story', year: 1995 },
- { label: 'Bicycle Thieves', year: 1948 },
- { label: 'The Kid', year: 1921 },
- { label: 'Inglourious Basterds', year: 2009 },
- { label: 'Snatch', year: 2000 },
- { label: '3 Idiots', year: 2009 },
- { label: 'Monty Python and the Holy Grail', year: 1975 },
-];
diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.tsx b/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.tsx
deleted file mode 100644
index cf1dfe2391..0000000000
--- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.tsx
+++ /dev/null
@@ -1,261 +0,0 @@
-import * as React from 'react';
-import {
- useAutocomplete,
- UseAutocompleteProps,
-} from '@base_ui/react/useAutocomplete';
-import { Button } from '@base_ui/react/Button';
-import { Popper } from '@base_ui/react/Popper';
-import { unstable_useForkRef as useForkRef } from '@mui/utils';
-import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
-import ClearIcon from '@mui/icons-material/Clear';
-import clsx from 'clsx';
-
-const Autocomplete = React.forwardRef(function Autocomplete(
- props: UseAutocompleteProps<(typeof top100Films)[number], false, false, false>,
- ref: React.ForwardedRef,
-) {
- const {
- disableClearable = false,
- disabled = false,
- readOnly = false,
- options,
- isOptionEqualToValue,
- ...other
- } = props;
-
- const {
- getRootProps,
- getInputProps,
- getPopupIndicatorProps,
- getClearProps,
- getListboxProps,
- getOptionProps,
- dirty,
- id,
- popupOpen,
- focused,
- anchorEl,
- setAnchorEl,
- groupedOptions,
- } = useAutocomplete({
- ...props,
- componentName: 'BaseAutocompleteIntroduction',
- });
-
- const hasClearIcon = !disableClearable && !disabled && dirty && !readOnly;
-
- const rootRef = useForkRef(ref, setAnchorEl);
-
- return (
-
-
-
- {hasClearIcon && (
-
-
-
- )}
-
-
-
-
- {anchorEl && (
-
-
- {(groupedOptions as typeof top100Films).map((option, index) => {
- const optionProps = getOptionProps({ option, index });
-
- return (
-
- {option.label}
-
- );
- })}
-
- {groupedOptions.length === 0 && (
- No results
- )}
-
-
- )}
-
- );
-});
-
-export default function AutocompleteIntroduction() {
- return (
- option.label === value.label}
- />
- );
-}
-
-const top100Films = [
- { label: 'The Shawshank Redemption', year: 1994 },
- { label: 'The Godfather', year: 1972 },
- { label: 'The Godfather: Part II', year: 1974 },
- { label: 'The Dark Knight', year: 2008 },
- { label: '12 Angry Men', year: 1957 },
- { label: "Schindler's List", year: 1993 },
- { label: 'Pulp Fiction', year: 1994 },
- {
- label: 'The Lord of the Rings: The Return of the King',
- year: 2003,
- },
- { label: 'The Good, the Bad and the Ugly', year: 1966 },
- { label: 'Fight Club', year: 1999 },
- {
- label: 'The Lord of the Rings: The Fellowship of the Ring',
- year: 2001,
- },
- {
- label: 'Star Wars: Episode V - The Empire Strikes Back',
- year: 1980,
- },
- { label: 'Forrest Gump', year: 1994 },
- { label: 'Inception', year: 2010 },
- {
- label: 'The Lord of the Rings: The Two Towers',
- year: 2002,
- },
- { label: "One Flew Over the Cuckoo's Nest", year: 1975 },
- { label: 'Goodfellas', year: 1990 },
- { label: 'The Matrix', year: 1999 },
- { label: 'Seven Samurai', year: 1954 },
- {
- label: 'Star Wars: Episode IV - A New Hope',
- year: 1977,
- },
- { label: 'City of God', year: 2002 },
- { label: 'Se7en', year: 1995 },
- { label: 'The Silence of the Lambs', year: 1991 },
- { label: "It's a Wonderful Life", year: 1946 },
- { label: 'Life Is Beautiful', year: 1997 },
- { label: 'The Usual Suspects', year: 1995 },
- { label: 'Léon: The Professional', year: 1994 },
- { label: 'Spirited Away', year: 2001 },
- { label: 'Saving Private Ryan', year: 1998 },
- { label: 'Once Upon a Time in the West', year: 1968 },
- { label: 'American History X', year: 1998 },
- { label: 'Interstellar', year: 2014 },
- { label: 'Casablanca', year: 1942 },
- { label: 'City Lights', year: 1931 },
- { label: 'Psycho', year: 1960 },
- { label: 'The Green Mile', year: 1999 },
- { label: 'The Intouchables', year: 2011 },
- { label: 'Modern Times', year: 1936 },
- { label: 'Raiders of the Lost Ark', year: 1981 },
- { label: 'Rear Window', year: 1954 },
- { label: 'The Pianist', year: 2002 },
- { label: 'The Departed', year: 2006 },
- { label: 'Terminator 2: Judgment Day', year: 1991 },
- { label: 'Back to the Future', year: 1985 },
- { label: 'Whiplash', year: 2014 },
- { label: 'Gladiator', year: 2000 },
- { label: 'Memento', year: 2000 },
- { label: 'The Prestige', year: 2006 },
- { label: 'The Lion King', year: 1994 },
- { label: 'Apocalypse Now', year: 1979 },
- { label: 'Alien', year: 1979 },
- { label: 'Sunset Boulevard', year: 1950 },
- {
- label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb',
- year: 1964,
- },
- { label: 'The Great Dictator', year: 1940 },
- { label: 'Cinema Paradiso', year: 1988 },
- { label: 'The Lives of Others', year: 2006 },
- { label: 'Grave of the Fireflies', year: 1988 },
- { label: 'Paths of Glory', year: 1957 },
- { label: 'Django Unchained', year: 2012 },
- { label: 'The Shining', year: 1980 },
- { label: 'WALL·E', year: 2008 },
- { label: 'American Beauty', year: 1999 },
- { label: 'The Dark Knight Rises', year: 2012 },
- { label: 'Princess Mononoke', year: 1997 },
- { label: 'Aliens', year: 1986 },
- { label: 'Oldboy', year: 2003 },
- { label: 'Once Upon a Time in America', year: 1984 },
- { label: 'Witness for the Prosecution', year: 1957 },
- { label: 'Das Boot', year: 1981 },
- { label: 'Citizen Kane', year: 1941 },
- { label: 'North by Northwest', year: 1959 },
- { label: 'Vertigo', year: 1958 },
- {
- label: 'Star Wars: Episode VI - Return of the Jedi',
- year: 1983,
- },
- { label: 'Reservoir Dogs', year: 1992 },
- { label: 'Braveheart', year: 1995 },
- { label: 'M', year: 1931 },
- { label: 'Requiem for a Dream', year: 2000 },
- { label: 'Amélie', year: 2001 },
- { label: 'A Clockwork Orange', year: 1971 },
- { label: 'Like Stars on Earth', year: 2007 },
- { label: 'Taxi Driver', year: 1976 },
- { label: 'Lawrence of Arabia', year: 1962 },
- { label: 'Double Indemnity', year: 1944 },
- {
- label: 'Eternal Sunshine of the Spotless Mind',
- year: 2004,
- },
- { label: 'Amadeus', year: 1984 },
- { label: 'To Kill a Mockingbird', year: 1962 },
- { label: 'Toy Story 3', year: 2010 },
- { label: 'Logan', year: 2017 },
- { label: 'Full Metal Jacket', year: 1987 },
- { label: 'Dangal', year: 2016 },
- { label: 'The Sting', year: 1973 },
- { label: '2001: A Space Odyssey', year: 1968 },
- { label: "Singin' in the Rain", year: 1952 },
- { label: 'Toy Story', year: 1995 },
- { label: 'Bicycle Thieves', year: 1948 },
- { label: 'The Kid', year: 1921 },
- { label: 'Inglourious Basterds', year: 2009 },
- { label: 'Snatch', year: 2000 },
- { label: '3 Idiots', year: 2009 },
- { label: 'Monty Python and the Holy Grail', year: 1975 },
-];
diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.tsx.preview b/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.tsx.preview
deleted file mode 100644
index 78a5cab783..0000000000
--- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.tsx.preview
+++ /dev/null
@@ -1,4 +0,0 @@
- option.label === value.label}
-/>
\ No newline at end of file
diff --git a/docs/data/base/components/autocomplete/ControlledStates.js b/docs/data/base/components/autocomplete/ControlledStates.js
deleted file mode 100644
index 039291df3e..0000000000
--- a/docs/data/base/components/autocomplete/ControlledStates.js
+++ /dev/null
@@ -1,209 +0,0 @@
-import * as React from 'react';
-import { useAutocomplete } from '@base_ui/react/useAutocomplete';
-import { styled } from '@mui/system';
-
-const options = ['Firefox', 'Google Chrome', 'Microsoft Edge', 'Safari', 'Opera'];
-
-export default function ControlledStates() {
- const [value, setValue] = React.useState(options[0]);
- const [inputValue, setInputValue] = React.useState('');
-
- const {
- getRootProps,
- getInputProps,
- getListboxProps,
- getOptionProps,
- groupedOptions,
- focused,
- } = useAutocomplete({
- id: 'controlled-state-demo',
- options,
- value,
- onChange: (event, newValue) => setValue(newValue),
- inputValue,
- onInputChange: (event, newInputValue) => setInputValue(newInputValue),
- });
-
- return (
-
-
- value: {value ?? ' '}
-
-
- inputValue: {inputValue ?? ' '}
-
-
-
-
-
- {groupedOptions.length > 0 && (
-
- {groupedOptions.map((option, index) => (
- {option}
- ))}
-
- )}
-
-
- );
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#99CCF3',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
- 900: '#003A75',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const AutocompleteWrapper = styled('div')`
- position: relative;
-`;
-
-const AutocompleteRoot = styled('div')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 400;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
- display: flex;
- gap: 5px;
- padding-right: 5px;
- overflow: hidden;
- width: 320px;
-
- &.Mui-focused {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]};
- }
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus-visible {
- outline: 0;
- }
-`,
-);
-
-const Input = styled('input')(
- ({ theme }) => `
- font-size: 0.875rem;
- font-family: inherit;
- font-weight: 400;
- line-height: 1.5;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: inherit;
- border: none;
- border-radius: inherit;
- padding: 8px 12px;
- outline: 0;
- flex: 1 0 auto;
-`,
-);
-
-const Listbox = styled('ul')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- box-sizing: border-box;
- padding: 6px;
- margin: 12px 0;
- max-width: 320px;
- border-radius: 12px;
- overflow: auto;
- outline: 0px;
- max-height: 300px;
- z-index: 1;
- position: absolute;
- left: 0;
- right: 0;
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- box-shadow: 0px 4px 6px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.50)' : 'rgba(0,0,0, 0.05)'
- };
- `,
-);
-
-const Option = styled('li')(
- ({ theme }) => `
- list-style: none;
- padding: 8px;
- border-radius: 8px;
- cursor: default;
-
- &:last-of-type {
- border-bottom: none;
- }
-
- &:hover {
- cursor: pointer;
- }
-
- &[aria-selected=true] {
- background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
- color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
- }
-
- &.Mui-focused,
- &.Mui-focusVisible {
- background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- }
-
- &.Mui-focusVisible {
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]};
- }
-
- &[aria-selected=true].Mui-focused,
- &[aria-selected=true].Mui-focusVisible {
- background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
- color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
- }
- `,
-);
-
-const Layout = styled('div')`
- display: flex;
- flex-flow: column nowrap;
- gap: 4px;
-`;
-
-const Pre = styled('pre')(({ theme }) => ({
- margin: '0.5rem 0',
- fontSize: '0.75rem',
- '& code': {
- backgroundColor: theme.palette.mode === 'light' ? grey[100] : grey[900],
- border: '1px solid',
- borderColor: theme.palette.mode === 'light' ? grey[300] : grey[700],
- color: theme.palette.mode === 'light' ? '#000' : '#fff',
- padding: '0.125rem 0.25rem',
- borderRadius: 3,
- },
-}));
diff --git a/docs/data/base/components/autocomplete/ControlledStates.tsx b/docs/data/base/components/autocomplete/ControlledStates.tsx
deleted file mode 100644
index b5044424e0..0000000000
--- a/docs/data/base/components/autocomplete/ControlledStates.tsx
+++ /dev/null
@@ -1,209 +0,0 @@
-import * as React from 'react';
-import { useAutocomplete } from '@base_ui/react/useAutocomplete';
-import { styled } from '@mui/system';
-
-const options = ['Firefox', 'Google Chrome', 'Microsoft Edge', 'Safari', 'Opera'];
-
-export default function ControlledStates() {
- const [value, setValue] = React.useState(options[0]);
- const [inputValue, setInputValue] = React.useState('');
-
- const {
- getRootProps,
- getInputProps,
- getListboxProps,
- getOptionProps,
- groupedOptions,
- focused,
- } = useAutocomplete({
- id: 'controlled-state-demo',
- options,
- value,
- onChange: (event, newValue) => setValue(newValue),
- inputValue,
- onInputChange: (event, newInputValue) => setInputValue(newInputValue),
- });
-
- return (
-
-
- value: {value ?? ' '}
-
-
- inputValue: {inputValue ?? ' '}
-
-
-
-
-
- {groupedOptions.length > 0 && (
-
- {(groupedOptions as string[]).map((option, index) => (
- {option}
- ))}
-
- )}
-
-
- );
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#99CCF3',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
- 900: '#003A75',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const AutocompleteWrapper = styled('div')`
- position: relative;
-`;
-
-const AutocompleteRoot = styled('div')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 400;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
- display: flex;
- gap: 5px;
- padding-right: 5px;
- overflow: hidden;
- width: 320px;
-
- &.Mui-focused {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]};
- }
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus-visible {
- outline: 0;
- }
-`,
-);
-
-const Input = styled('input')(
- ({ theme }) => `
- font-size: 0.875rem;
- font-family: inherit;
- font-weight: 400;
- line-height: 1.5;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: inherit;
- border: none;
- border-radius: inherit;
- padding: 8px 12px;
- outline: 0;
- flex: 1 0 auto;
-`,
-);
-
-const Listbox = styled('ul')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- box-sizing: border-box;
- padding: 6px;
- margin: 12px 0;
- max-width: 320px;
- border-radius: 12px;
- overflow: auto;
- outline: 0px;
- max-height: 300px;
- z-index: 1;
- position: absolute;
- left: 0;
- right: 0;
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- box-shadow: 0px 4px 6px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.50)' : 'rgba(0,0,0, 0.05)'
- };
- `,
-);
-
-const Option = styled('li')(
- ({ theme }) => `
- list-style: none;
- padding: 8px;
- border-radius: 8px;
- cursor: default;
-
- &:last-of-type {
- border-bottom: none;
- }
-
- &:hover {
- cursor: pointer;
- }
-
- &[aria-selected=true] {
- background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
- color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
- }
-
- &.Mui-focused,
- &.Mui-focusVisible {
- background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- }
-
- &.Mui-focusVisible {
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]};
- }
-
- &[aria-selected=true].Mui-focused,
- &[aria-selected=true].Mui-focusVisible {
- background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
- color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
- }
- `,
-);
-
-const Layout = styled('div')`
- display: flex;
- flex-flow: column nowrap;
- gap: 4px;
-`;
-
-const Pre = styled('pre')(({ theme }) => ({
- margin: '0.5rem 0',
- fontSize: '0.75rem',
- '& code': {
- backgroundColor: theme.palette.mode === 'light' ? grey[100] : grey[900],
- border: '1px solid',
- borderColor: theme.palette.mode === 'light' ? grey[300] : grey[700],
- color: theme.palette.mode === 'light' ? '#000' : '#fff',
- padding: '0.125rem 0.25rem',
- borderRadius: 3,
- },
-}));
diff --git a/docs/data/base/components/autocomplete/UseAutocomplete.js b/docs/data/base/components/autocomplete/UseAutocomplete.js
deleted file mode 100644
index 7ca9074295..0000000000
--- a/docs/data/base/components/autocomplete/UseAutocomplete.js
+++ /dev/null
@@ -1,307 +0,0 @@
-import * as React from 'react';
-import { useAutocomplete } from '@base_ui/react/useAutocomplete';
-import { styled } from '@mui/system';
-
-export default function UseAutocomplete() {
- const [value, setValue] = React.useState(null);
-
- const {
- getRootProps,
- getInputLabelProps,
- getInputProps,
- getListboxProps,
- getOptionProps,
- groupedOptions,
- focused,
- } = useAutocomplete({
- id: 'use-autocomplete-demo',
- options: top100Films,
- getOptionLabel: (option) => option.label,
- value,
- onChange: (event, newValue) => setValue(newValue),
- });
-
- return (
-
- Pick a movie
-
-
-
- {groupedOptions.length > 0 && (
-
- {groupedOptions.map((option, index) => (
- {option.label}
- ))}
-
- )}
-
- );
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#99CCF3',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
- 900: '#003A75',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Label = styled('label')`
- display: block;
- font-family: sans-serif;
- font-size: 14px;
- font-weight: 500;
- margin-bottom: 4px;
-`;
-
-const Root = styled('div')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 400;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
- display: flex;
- gap: 5px;
- padding-right: 5px;
- overflow: hidden;
- width: 320px;
-
- &.Mui-focused {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
- }
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus-visible {
- outline: 0;
- }
-`,
-);
-
-const Input = styled('input')(
- ({ theme }) => `
- font-size: 0.875rem;
- font-family: inherit;
- font-weight: 400;
- line-height: 1.5;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: inherit;
- border: none;
- border-radius: inherit;
- padding: 8px 12px;
- outline: 0;
- flex: 1 0 auto;
-`,
-);
-
-const Listbox = styled('ul')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- box-sizing: border-box;
- padding: 6px;
- margin: 12px 0;
- width: 320px;
- border-radius: 12px;
- overflow: auto;
- outline: 0px;
- max-height: 300px;
- z-index: 1;
- position: absolute;
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- box-shadow: 0px 2px 3px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.50)' : 'rgba(0,0,0, 0.05)'
- };
- `,
-);
-
-const Option = styled('li')(
- ({ theme }) => `
- list-style: none;
- padding: 8px;
- border-radius: 8px;
- cursor: default;
-
- &:last-of-type {
- border-bottom: none;
- }
-
- &:hover {
- cursor: pointer;
- }
-
- &[aria-selected=true] {
- background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
- color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
- }
-
- &.Mui-focused,
- &.Mui-focusVisible {
- background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- }
-
- &.Mui-focusVisible {
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]};
- }
-
- &[aria-selected=true].Mui-focused,
- &[aria-selected=true].Mui-focusVisible {
- background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
- color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
- }
- `,
-);
-
-// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top
-const top100Films = [
- { label: 'The Shawshank Redemption', year: 1994 },
- { label: 'The Godfather', year: 1972 },
- { label: 'The Godfather: Part II', year: 1974 },
- { label: 'The Dark Knight', year: 2008 },
- { label: '12 Angry Men', year: 1957 },
- { label: "Schindler's List", year: 1993 },
- { label: 'Pulp Fiction', year: 1994 },
- {
- label: 'The Lord of the Rings: The Return of the King',
- year: 2003,
- },
- { label: 'The Good, the Bad and the Ugly', year: 1966 },
- { label: 'Fight Club', year: 1999 },
- {
- label: 'The Lord of the Rings: The Fellowship of the Ring',
- year: 2001,
- },
- {
- label: 'Star Wars: Episode V - The Empire Strikes Back',
- year: 1980,
- },
- { label: 'Forrest Gump', year: 1994 },
- { label: 'Inception', year: 2010 },
- {
- label: 'The Lord of the Rings: The Two Towers',
- year: 2002,
- },
- { label: "One Flew Over the Cuckoo's Nest", year: 1975 },
- { label: 'Goodfellas', year: 1990 },
- { label: 'The Matrix', year: 1999 },
- { label: 'Seven Samurai', year: 1954 },
- {
- label: 'Star Wars: Episode IV - A New Hope',
- year: 1977,
- },
- { label: 'City of God', year: 2002 },
- { label: 'Se7en', year: 1995 },
- { label: 'The Silence of the Lambs', year: 1991 },
- { label: "It's a Wonderful Life", year: 1946 },
- { label: 'Life Is Beautiful', year: 1997 },
- { label: 'The Usual Suspects', year: 1995 },
- { label: 'Léon: The Professional', year: 1994 },
- { label: 'Spirited Away', year: 2001 },
- { label: 'Saving Private Ryan', year: 1998 },
- { label: 'Once Upon a Time in the West', year: 1968 },
- { label: 'American History X', year: 1998 },
- { label: 'Interstellar', year: 2014 },
- { label: 'Casablanca', year: 1942 },
- { label: 'City Lights', year: 1931 },
- { label: 'Psycho', year: 1960 },
- { label: 'The Green Mile', year: 1999 },
- { label: 'The Intouchables', year: 2011 },
- { label: 'Modern Times', year: 1936 },
- { label: 'Raiders of the Lost Ark', year: 1981 },
- { label: 'Rear Window', year: 1954 },
- { label: 'The Pianist', year: 2002 },
- { label: 'The Departed', year: 2006 },
- { label: 'Terminator 2: Judgment Day', year: 1991 },
- { label: 'Back to the Future', year: 1985 },
- { label: 'Whiplash', year: 2014 },
- { label: 'Gladiator', year: 2000 },
- { label: 'Memento', year: 2000 },
- { label: 'The Prestige', year: 2006 },
- { label: 'The Lion King', year: 1994 },
- { label: 'Apocalypse Now', year: 1979 },
- { label: 'Alien', year: 1979 },
- { label: 'Sunset Boulevard', year: 1950 },
- {
- label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb',
- year: 1964,
- },
- { label: 'The Great Dictator', year: 1940 },
- { label: 'Cinema Paradiso', year: 1988 },
- { label: 'The Lives of Others', year: 2006 },
- { label: 'Grave of the Fireflies', year: 1988 },
- { label: 'Paths of Glory', year: 1957 },
- { label: 'Django Unchained', year: 2012 },
- { label: 'The Shining', year: 1980 },
- { label: 'WALL·E', year: 2008 },
- { label: 'American Beauty', year: 1999 },
- { label: 'The Dark Knight Rises', year: 2012 },
- { label: 'Princess Mononoke', year: 1997 },
- { label: 'Aliens', year: 1986 },
- { label: 'Oldboy', year: 2003 },
- { label: 'Once Upon a Time in America', year: 1984 },
- { label: 'Witness for the Prosecution', year: 1957 },
- { label: 'Das Boot', year: 1981 },
- { label: 'Citizen Kane', year: 1941 },
- { label: 'North by Northwest', year: 1959 },
- { label: 'Vertigo', year: 1958 },
- {
- label: 'Star Wars: Episode VI - Return of the Jedi',
- year: 1983,
- },
- { label: 'Reservoir Dogs', year: 1992 },
- { label: 'Braveheart', year: 1995 },
- { label: 'M', year: 1931 },
- { label: 'Requiem for a Dream', year: 2000 },
- { label: 'Amélie', year: 2001 },
- { label: 'A Clockwork Orange', year: 1971 },
- { label: 'Like Stars on Earth', year: 2007 },
- { label: 'Taxi Driver', year: 1976 },
- { label: 'Lawrence of Arabia', year: 1962 },
- { label: 'Double Indemnity', year: 1944 },
- {
- label: 'Eternal Sunshine of the Spotless Mind',
- year: 2004,
- },
- { label: 'Amadeus', year: 1984 },
- { label: 'To Kill a Mockingbird', year: 1962 },
- { label: 'Toy Story 3', year: 2010 },
- { label: 'Logan', year: 2017 },
- { label: 'Full Metal Jacket', year: 1987 },
- { label: 'Dangal', year: 2016 },
- { label: 'The Sting', year: 1973 },
- { label: '2001: A Space Odyssey', year: 1968 },
- { label: "Singin' in the Rain", year: 1952 },
- { label: 'Toy Story', year: 1995 },
- { label: 'Bicycle Thieves', year: 1948 },
- { label: 'The Kid', year: 1921 },
- { label: 'Inglourious Basterds', year: 2009 },
- { label: 'Snatch', year: 2000 },
- { label: '3 Idiots', year: 2009 },
- { label: 'Monty Python and the Holy Grail', year: 1975 },
-];
diff --git a/docs/data/base/components/autocomplete/UseAutocomplete.tsx b/docs/data/base/components/autocomplete/UseAutocomplete.tsx
deleted file mode 100644
index d0b212c1cf..0000000000
--- a/docs/data/base/components/autocomplete/UseAutocomplete.tsx
+++ /dev/null
@@ -1,309 +0,0 @@
-import * as React from 'react';
-import { useAutocomplete } from '@base_ui/react/useAutocomplete';
-import { styled } from '@mui/system';
-
-export default function UseAutocomplete() {
- const [value, setValue] = React.useState<(typeof top100Films)[number] | null>(
- null,
- );
-
- const {
- getRootProps,
- getInputLabelProps,
- getInputProps,
- getListboxProps,
- getOptionProps,
- groupedOptions,
- focused,
- } = useAutocomplete({
- id: 'use-autocomplete-demo',
- options: top100Films,
- getOptionLabel: (option) => option.label,
- value,
- onChange: (event, newValue) => setValue(newValue),
- });
-
- return (
-
- Pick a movie
-
-
-
- {groupedOptions.length > 0 && (
-
- {(groupedOptions as typeof top100Films).map((option, index) => (
- {option.label}
- ))}
-
- )}
-
- );
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#99CCF3',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
- 900: '#003A75',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Label = styled('label')`
- display: block;
- font-family: sans-serif;
- font-size: 14px;
- font-weight: 500;
- margin-bottom: 4px;
-`;
-
-const Root = styled('div')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 400;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
- display: flex;
- gap: 5px;
- padding-right: 5px;
- overflow: hidden;
- width: 320px;
-
- &.Mui-focused {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
- }
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus-visible {
- outline: 0;
- }
-`,
-);
-
-const Input = styled('input')(
- ({ theme }) => `
- font-size: 0.875rem;
- font-family: inherit;
- font-weight: 400;
- line-height: 1.5;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: inherit;
- border: none;
- border-radius: inherit;
- padding: 8px 12px;
- outline: 0;
- flex: 1 0 auto;
-`,
-);
-
-const Listbox = styled('ul')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- box-sizing: border-box;
- padding: 6px;
- margin: 12px 0;
- width: 320px;
- border-radius: 12px;
- overflow: auto;
- outline: 0px;
- max-height: 300px;
- z-index: 1;
- position: absolute;
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- box-shadow: 0px 2px 3px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.50)' : 'rgba(0,0,0, 0.05)'
- };
- `,
-);
-
-const Option = styled('li')(
- ({ theme }) => `
- list-style: none;
- padding: 8px;
- border-radius: 8px;
- cursor: default;
-
- &:last-of-type {
- border-bottom: none;
- }
-
- &:hover {
- cursor: pointer;
- }
-
- &[aria-selected=true] {
- background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
- color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
- }
-
- &.Mui-focused,
- &.Mui-focusVisible {
- background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- }
-
- &.Mui-focusVisible {
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]};
- }
-
- &[aria-selected=true].Mui-focused,
- &[aria-selected=true].Mui-focusVisible {
- background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
- color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
- }
- `,
-);
-
-// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top
-const top100Films = [
- { label: 'The Shawshank Redemption', year: 1994 },
- { label: 'The Godfather', year: 1972 },
- { label: 'The Godfather: Part II', year: 1974 },
- { label: 'The Dark Knight', year: 2008 },
- { label: '12 Angry Men', year: 1957 },
- { label: "Schindler's List", year: 1993 },
- { label: 'Pulp Fiction', year: 1994 },
- {
- label: 'The Lord of the Rings: The Return of the King',
- year: 2003,
- },
- { label: 'The Good, the Bad and the Ugly', year: 1966 },
- { label: 'Fight Club', year: 1999 },
- {
- label: 'The Lord of the Rings: The Fellowship of the Ring',
- year: 2001,
- },
- {
- label: 'Star Wars: Episode V - The Empire Strikes Back',
- year: 1980,
- },
- { label: 'Forrest Gump', year: 1994 },
- { label: 'Inception', year: 2010 },
- {
- label: 'The Lord of the Rings: The Two Towers',
- year: 2002,
- },
- { label: "One Flew Over the Cuckoo's Nest", year: 1975 },
- { label: 'Goodfellas', year: 1990 },
- { label: 'The Matrix', year: 1999 },
- { label: 'Seven Samurai', year: 1954 },
- {
- label: 'Star Wars: Episode IV - A New Hope',
- year: 1977,
- },
- { label: 'City of God', year: 2002 },
- { label: 'Se7en', year: 1995 },
- { label: 'The Silence of the Lambs', year: 1991 },
- { label: "It's a Wonderful Life", year: 1946 },
- { label: 'Life Is Beautiful', year: 1997 },
- { label: 'The Usual Suspects', year: 1995 },
- { label: 'Léon: The Professional', year: 1994 },
- { label: 'Spirited Away', year: 2001 },
- { label: 'Saving Private Ryan', year: 1998 },
- { label: 'Once Upon a Time in the West', year: 1968 },
- { label: 'American History X', year: 1998 },
- { label: 'Interstellar', year: 2014 },
- { label: 'Casablanca', year: 1942 },
- { label: 'City Lights', year: 1931 },
- { label: 'Psycho', year: 1960 },
- { label: 'The Green Mile', year: 1999 },
- { label: 'The Intouchables', year: 2011 },
- { label: 'Modern Times', year: 1936 },
- { label: 'Raiders of the Lost Ark', year: 1981 },
- { label: 'Rear Window', year: 1954 },
- { label: 'The Pianist', year: 2002 },
- { label: 'The Departed', year: 2006 },
- { label: 'Terminator 2: Judgment Day', year: 1991 },
- { label: 'Back to the Future', year: 1985 },
- { label: 'Whiplash', year: 2014 },
- { label: 'Gladiator', year: 2000 },
- { label: 'Memento', year: 2000 },
- { label: 'The Prestige', year: 2006 },
- { label: 'The Lion King', year: 1994 },
- { label: 'Apocalypse Now', year: 1979 },
- { label: 'Alien', year: 1979 },
- { label: 'Sunset Boulevard', year: 1950 },
- {
- label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb',
- year: 1964,
- },
- { label: 'The Great Dictator', year: 1940 },
- { label: 'Cinema Paradiso', year: 1988 },
- { label: 'The Lives of Others', year: 2006 },
- { label: 'Grave of the Fireflies', year: 1988 },
- { label: 'Paths of Glory', year: 1957 },
- { label: 'Django Unchained', year: 2012 },
- { label: 'The Shining', year: 1980 },
- { label: 'WALL·E', year: 2008 },
- { label: 'American Beauty', year: 1999 },
- { label: 'The Dark Knight Rises', year: 2012 },
- { label: 'Princess Mononoke', year: 1997 },
- { label: 'Aliens', year: 1986 },
- { label: 'Oldboy', year: 2003 },
- { label: 'Once Upon a Time in America', year: 1984 },
- { label: 'Witness for the Prosecution', year: 1957 },
- { label: 'Das Boot', year: 1981 },
- { label: 'Citizen Kane', year: 1941 },
- { label: 'North by Northwest', year: 1959 },
- { label: 'Vertigo', year: 1958 },
- {
- label: 'Star Wars: Episode VI - Return of the Jedi',
- year: 1983,
- },
- { label: 'Reservoir Dogs', year: 1992 },
- { label: 'Braveheart', year: 1995 },
- { label: 'M', year: 1931 },
- { label: 'Requiem for a Dream', year: 2000 },
- { label: 'Amélie', year: 2001 },
- { label: 'A Clockwork Orange', year: 1971 },
- { label: 'Like Stars on Earth', year: 2007 },
- { label: 'Taxi Driver', year: 1976 },
- { label: 'Lawrence of Arabia', year: 1962 },
- { label: 'Double Indemnity', year: 1944 },
- {
- label: 'Eternal Sunshine of the Spotless Mind',
- year: 2004,
- },
- { label: 'Amadeus', year: 1984 },
- { label: 'To Kill a Mockingbird', year: 1962 },
- { label: 'Toy Story 3', year: 2010 },
- { label: 'Logan', year: 2017 },
- { label: 'Full Metal Jacket', year: 1987 },
- { label: 'Dangal', year: 2016 },
- { label: 'The Sting', year: 1973 },
- { label: '2001: A Space Odyssey', year: 1968 },
- { label: "Singin' in the Rain", year: 1952 },
- { label: 'Toy Story', year: 1995 },
- { label: 'Bicycle Thieves', year: 1948 },
- { label: 'The Kid', year: 1921 },
- { label: 'Inglourious Basterds', year: 2009 },
- { label: 'Snatch', year: 2000 },
- { label: '3 Idiots', year: 2009 },
- { label: 'Monty Python and the Holy Grail', year: 1975 },
-];
diff --git a/docs/data/base/components/autocomplete/UseAutocomplete.tsx.preview b/docs/data/base/components/autocomplete/UseAutocomplete.tsx.preview
deleted file mode 100644
index 7c000513c9..0000000000
--- a/docs/data/base/components/autocomplete/UseAutocomplete.tsx.preview
+++ /dev/null
@@ -1,11 +0,0 @@
-Pick a movie
-
-
-
-{groupedOptions.length > 0 && (
-
- {(groupedOptions as typeof top100Films).map((option, index) => (
- {option.label}
- ))}
-
-)}
\ No newline at end of file
diff --git a/docs/data/base/components/autocomplete/UseAutocompletePopper.js b/docs/data/base/components/autocomplete/UseAutocompletePopper.js
deleted file mode 100644
index 0c5385b8bd..0000000000
--- a/docs/data/base/components/autocomplete/UseAutocompletePopper.js
+++ /dev/null
@@ -1,337 +0,0 @@
-import * as React from 'react';
-import { useAutocomplete } from '@base_ui/react/useAutocomplete';
-import { Popper } from '@base_ui/react/Popper';
-import { styled } from '@mui/system';
-import useForkRef from '@mui/utils/useForkRef';
-
-const Autocomplete = React.forwardRef(function Autocomplete(props, ref) {
- const {
- getRootProps,
- getInputProps,
- getListboxProps,
- getOptionProps,
- groupedOptions,
- focused,
- popupOpen,
- anchorEl,
- setAnchorEl,
- } = useAutocomplete(props);
-
- const rootRef = useForkRef(ref, setAnchorEl);
-
- return (
-
-
-
-
- {anchorEl && (
-
-
- {groupedOptions.length > 0 ? (
- groupedOptions.map((option, index) => (
-
- {option.label}
-
- ))
- ) : (
- No results
- )}
-
-
- )}
-
- );
-});
-
-export default function UseAutocompletePopper() {
- const [value, setValue] = React.useState(null);
-
- const handleChange = (event, newValue) => setValue(newValue);
-
- return (
-
- );
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#99CCF3',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
- 900: '#003A75',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Root = styled('div')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 400;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
- display: flex;
- gap: 5px;
- padding-right: 5px;
- overflow: hidden;
- width: 320px;
- margin: 1.5rem 0;
-
- &.Mui-focused {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]};
- }
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus-visible {
- outline: 0;
- }
-`,
-);
-
-const StyledInput = styled('input')(
- ({ theme }) => `
- font-size: 0.875rem;
- font-family: inherit;
- font-weight: 400;
- line-height: 1.5;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: inherit;
- border: none;
- border-radius: inherit;
- padding: 8px 12px;
- outline: 0;
- flex: 1 0 auto;
-`,
-);
-
-// ComponentPageTabs has z-index: 1000
-const StyledPopper = styled('div')`
- position: relative;
- z-index: 1001;
- width: 320px;
-`;
-
-const Listbox = styled('ul')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- box-sizing: border-box;
- padding: 6px;
- margin: 12px 0;
- min-width: 320px;
- border-radius: 12px;
- overflow: auto;
- outline: 0px;
- max-height: 300px;
- z-index: 1;
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- box-shadow: 0px 4px 6px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.50)' : 'rgba(0,0,0, 0.05)'
- };
- `,
-);
-
-const Option = styled('li')(
- ({ theme }) => `
- list-style: none;
- padding: 8px;
- border-radius: 8px;
- cursor: default;
-
- &:last-of-type {
- border-bottom: none;
- }
-
- &:hover {
- cursor: pointer;
- }
-
- &[aria-selected=true] {
- background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
- color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
- }
-
- &.Mui-focused,
- &.Mui-focusVisible {
- background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- }
-
- &.Mui-focusVisible {
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]};
- }
-
- &[aria-selected=true].Mui-focused,
- &[aria-selected=true].Mui-focusVisible {
- background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
- color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
- }
- `,
-);
-
-const NoOptions = styled('li')`
- list-style: none;
- padding: 8px;
- cursor: default;
-`;
-
-// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top
-const top100Films = [
- { label: 'The Shawshank Redemption', year: 1994 },
- { label: 'The Godfather', year: 1972 },
- { label: 'The Godfather: Part II', year: 1974 },
- { label: 'The Dark Knight', year: 2008 },
- { label: '12 Angry Men', year: 1957 },
- { label: "Schindler's List", year: 1993 },
- { label: 'Pulp Fiction', year: 1994 },
- {
- label: 'The Lord of the Rings: The Return of the King',
- year: 2003,
- },
- { label: 'The Good, the Bad and the Ugly', year: 1966 },
- { label: 'Fight Club', year: 1999 },
- {
- label: 'The Lord of the Rings: The Fellowship of the Ring',
- year: 2001,
- },
- {
- label: 'Star Wars: Episode V - The Empire Strikes Back',
- year: 1980,
- },
- { label: 'Forrest Gump', year: 1994 },
- { label: 'Inception', year: 2010 },
- {
- label: 'The Lord of the Rings: The Two Towers',
- year: 2002,
- },
- { label: "One Flew Over the Cuckoo's Nest", year: 1975 },
- { label: 'Goodfellas', year: 1990 },
- { label: 'The Matrix', year: 1999 },
- { label: 'Seven Samurai', year: 1954 },
- {
- label: 'Star Wars: Episode IV - A New Hope',
- year: 1977,
- },
- { label: 'City of God', year: 2002 },
- { label: 'Se7en', year: 1995 },
- { label: 'The Silence of the Lambs', year: 1991 },
- { label: "It's a Wonderful Life", year: 1946 },
- { label: 'Life Is Beautiful', year: 1997 },
- { label: 'The Usual Suspects', year: 1995 },
- { label: 'Léon: The Professional', year: 1994 },
- { label: 'Spirited Away', year: 2001 },
- { label: 'Saving Private Ryan', year: 1998 },
- { label: 'Once Upon a Time in the West', year: 1968 },
- { label: 'American History X', year: 1998 },
- { label: 'Interstellar', year: 2014 },
- { label: 'Casablanca', year: 1942 },
- { label: 'City Lights', year: 1931 },
- { label: 'Psycho', year: 1960 },
- { label: 'The Green Mile', year: 1999 },
- { label: 'The Intouchables', year: 2011 },
- { label: 'Modern Times', year: 1936 },
- { label: 'Raiders of the Lost Ark', year: 1981 },
- { label: 'Rear Window', year: 1954 },
- { label: 'The Pianist', year: 2002 },
- { label: 'The Departed', year: 2006 },
- { label: 'Terminator 2: Judgment Day', year: 1991 },
- { label: 'Back to the Future', year: 1985 },
- { label: 'Whiplash', year: 2014 },
- { label: 'Gladiator', year: 2000 },
- { label: 'Memento', year: 2000 },
- { label: 'The Prestige', year: 2006 },
- { label: 'The Lion King', year: 1994 },
- { label: 'Apocalypse Now', year: 1979 },
- { label: 'Alien', year: 1979 },
- { label: 'Sunset Boulevard', year: 1950 },
- {
- label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb',
- year: 1964,
- },
- { label: 'The Great Dictator', year: 1940 },
- { label: 'Cinema Paradiso', year: 1988 },
- { label: 'The Lives of Others', year: 2006 },
- { label: 'Grave of the Fireflies', year: 1988 },
- { label: 'Paths of Glory', year: 1957 },
- { label: 'Django Unchained', year: 2012 },
- { label: 'The Shining', year: 1980 },
- { label: 'WALL·E', year: 2008 },
- { label: 'American Beauty', year: 1999 },
- { label: 'The Dark Knight Rises', year: 2012 },
- { label: 'Princess Mononoke', year: 1997 },
- { label: 'Aliens', year: 1986 },
- { label: 'Oldboy', year: 2003 },
- { label: 'Once Upon a Time in America', year: 1984 },
- { label: 'Witness for the Prosecution', year: 1957 },
- { label: 'Das Boot', year: 1981 },
- { label: 'Citizen Kane', year: 1941 },
- { label: 'North by Northwest', year: 1959 },
- { label: 'Vertigo', year: 1958 },
- {
- label: 'Star Wars: Episode VI - Return of the Jedi',
- year: 1983,
- },
- { label: 'Reservoir Dogs', year: 1992 },
- { label: 'Braveheart', year: 1995 },
- { label: 'M', year: 1931 },
- { label: 'Requiem for a Dream', year: 2000 },
- { label: 'Amélie', year: 2001 },
- { label: 'A Clockwork Orange', year: 1971 },
- { label: 'Like Stars on Earth', year: 2007 },
- { label: 'Taxi Driver', year: 1976 },
- { label: 'Lawrence of Arabia', year: 1962 },
- { label: 'Double Indemnity', year: 1944 },
- {
- label: 'Eternal Sunshine of the Spotless Mind',
- year: 2004,
- },
- { label: 'Amadeus', year: 1984 },
- { label: 'To Kill a Mockingbird', year: 1962 },
- { label: 'Toy Story 3', year: 2010 },
- { label: 'Logan', year: 2017 },
- { label: 'Full Metal Jacket', year: 1987 },
- { label: 'Dangal', year: 2016 },
- { label: 'The Sting', year: 1973 },
- { label: '2001: A Space Odyssey', year: 1968 },
- { label: "Singin' in the Rain", year: 1952 },
- { label: 'Toy Story', year: 1995 },
- { label: 'Bicycle Thieves', year: 1948 },
- { label: 'The Kid', year: 1921 },
- { label: 'Inglourious Basterds', year: 2009 },
- { label: 'Snatch', year: 2000 },
- { label: '3 Idiots', year: 2009 },
- { label: 'Monty Python and the Holy Grail', year: 1975 },
-];
diff --git a/docs/data/base/components/autocomplete/UseAutocompletePopper.tsx b/docs/data/base/components/autocomplete/UseAutocompletePopper.tsx
deleted file mode 100644
index 7b9333884a..0000000000
--- a/docs/data/base/components/autocomplete/UseAutocompletePopper.tsx
+++ /dev/null
@@ -1,348 +0,0 @@
-import * as React from 'react';
-import {
- useAutocomplete,
- UseAutocompleteProps,
-} from '@base_ui/react/useAutocomplete';
-import { Popper } from '@base_ui/react/Popper';
-import { styled } from '@mui/system';
-import useForkRef from '@mui/utils/useForkRef';
-
-const Autocomplete = React.forwardRef(function Autocomplete(
- props: UseAutocompleteProps<(typeof top100Films)[number], false, false, false>,
- ref: React.ForwardedRef,
-) {
- const {
- getRootProps,
- getInputProps,
- getListboxProps,
- getOptionProps,
- groupedOptions,
- focused,
- popupOpen,
- anchorEl,
- setAnchorEl,
- } = useAutocomplete(props);
-
- const rootRef = useForkRef(ref, setAnchorEl);
-
- return (
-
-
-
-
- {anchorEl && (
-
-
- {groupedOptions.length > 0 ? (
- (groupedOptions as typeof top100Films).map((option, index) => (
-
- {option.label}
-
- ))
- ) : (
- No results
- )}
-
-
- )}
-
- );
-});
-
-export default function UseAutocompletePopper() {
- const [value, setValue] = React.useState<(typeof top100Films)[number] | null>(
- null,
- );
-
- const handleChange = (
- event: React.SyntheticEvent,
- newValue: (typeof top100Films)[number] | null,
- ) => setValue(newValue);
-
- return (
-
- );
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#99CCF3',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
- 900: '#003A75',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Root = styled('div')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 400;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
- display: flex;
- gap: 5px;
- padding-right: 5px;
- overflow: hidden;
- width: 320px;
- margin: 1.5rem 0;
-
- &.Mui-focused {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]};
- }
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus-visible {
- outline: 0;
- }
-`,
-);
-
-const StyledInput = styled('input')(
- ({ theme }) => `
- font-size: 0.875rem;
- font-family: inherit;
- font-weight: 400;
- line-height: 1.5;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: inherit;
- border: none;
- border-radius: inherit;
- padding: 8px 12px;
- outline: 0;
- flex: 1 0 auto;
-`,
-);
-
-// ComponentPageTabs has z-index: 1000
-const StyledPopper = styled('div')`
- position: relative;
- z-index: 1001;
- width: 320px;
-`;
-
-const Listbox = styled('ul')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- box-sizing: border-box;
- padding: 6px;
- margin: 12px 0;
- min-width: 320px;
- border-radius: 12px;
- overflow: auto;
- outline: 0px;
- max-height: 300px;
- z-index: 1;
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- box-shadow: 0px 4px 6px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.50)' : 'rgba(0,0,0, 0.05)'
- };
- `,
-);
-
-const Option = styled('li')(
- ({ theme }) => `
- list-style: none;
- padding: 8px;
- border-radius: 8px;
- cursor: default;
-
- &:last-of-type {
- border-bottom: none;
- }
-
- &:hover {
- cursor: pointer;
- }
-
- &[aria-selected=true] {
- background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
- color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
- }
-
- &.Mui-focused,
- &.Mui-focusVisible {
- background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- }
-
- &.Mui-focusVisible {
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]};
- }
-
- &[aria-selected=true].Mui-focused,
- &[aria-selected=true].Mui-focusVisible {
- background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
- color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
- }
- `,
-);
-
-const NoOptions = styled('li')`
- list-style: none;
- padding: 8px;
- cursor: default;
-`;
-
-// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top
-const top100Films = [
- { label: 'The Shawshank Redemption', year: 1994 },
- { label: 'The Godfather', year: 1972 },
- { label: 'The Godfather: Part II', year: 1974 },
- { label: 'The Dark Knight', year: 2008 },
- { label: '12 Angry Men', year: 1957 },
- { label: "Schindler's List", year: 1993 },
- { label: 'Pulp Fiction', year: 1994 },
- {
- label: 'The Lord of the Rings: The Return of the King',
- year: 2003,
- },
- { label: 'The Good, the Bad and the Ugly', year: 1966 },
- { label: 'Fight Club', year: 1999 },
- {
- label: 'The Lord of the Rings: The Fellowship of the Ring',
- year: 2001,
- },
- {
- label: 'Star Wars: Episode V - The Empire Strikes Back',
- year: 1980,
- },
- { label: 'Forrest Gump', year: 1994 },
- { label: 'Inception', year: 2010 },
- {
- label: 'The Lord of the Rings: The Two Towers',
- year: 2002,
- },
- { label: "One Flew Over the Cuckoo's Nest", year: 1975 },
- { label: 'Goodfellas', year: 1990 },
- { label: 'The Matrix', year: 1999 },
- { label: 'Seven Samurai', year: 1954 },
- {
- label: 'Star Wars: Episode IV - A New Hope',
- year: 1977,
- },
- { label: 'City of God', year: 2002 },
- { label: 'Se7en', year: 1995 },
- { label: 'The Silence of the Lambs', year: 1991 },
- { label: "It's a Wonderful Life", year: 1946 },
- { label: 'Life Is Beautiful', year: 1997 },
- { label: 'The Usual Suspects', year: 1995 },
- { label: 'Léon: The Professional', year: 1994 },
- { label: 'Spirited Away', year: 2001 },
- { label: 'Saving Private Ryan', year: 1998 },
- { label: 'Once Upon a Time in the West', year: 1968 },
- { label: 'American History X', year: 1998 },
- { label: 'Interstellar', year: 2014 },
- { label: 'Casablanca', year: 1942 },
- { label: 'City Lights', year: 1931 },
- { label: 'Psycho', year: 1960 },
- { label: 'The Green Mile', year: 1999 },
- { label: 'The Intouchables', year: 2011 },
- { label: 'Modern Times', year: 1936 },
- { label: 'Raiders of the Lost Ark', year: 1981 },
- { label: 'Rear Window', year: 1954 },
- { label: 'The Pianist', year: 2002 },
- { label: 'The Departed', year: 2006 },
- { label: 'Terminator 2: Judgment Day', year: 1991 },
- { label: 'Back to the Future', year: 1985 },
- { label: 'Whiplash', year: 2014 },
- { label: 'Gladiator', year: 2000 },
- { label: 'Memento', year: 2000 },
- { label: 'The Prestige', year: 2006 },
- { label: 'The Lion King', year: 1994 },
- { label: 'Apocalypse Now', year: 1979 },
- { label: 'Alien', year: 1979 },
- { label: 'Sunset Boulevard', year: 1950 },
- {
- label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb',
- year: 1964,
- },
- { label: 'The Great Dictator', year: 1940 },
- { label: 'Cinema Paradiso', year: 1988 },
- { label: 'The Lives of Others', year: 2006 },
- { label: 'Grave of the Fireflies', year: 1988 },
- { label: 'Paths of Glory', year: 1957 },
- { label: 'Django Unchained', year: 2012 },
- { label: 'The Shining', year: 1980 },
- { label: 'WALL·E', year: 2008 },
- { label: 'American Beauty', year: 1999 },
- { label: 'The Dark Knight Rises', year: 2012 },
- { label: 'Princess Mononoke', year: 1997 },
- { label: 'Aliens', year: 1986 },
- { label: 'Oldboy', year: 2003 },
- { label: 'Once Upon a Time in America', year: 1984 },
- { label: 'Witness for the Prosecution', year: 1957 },
- { label: 'Das Boot', year: 1981 },
- { label: 'Citizen Kane', year: 1941 },
- { label: 'North by Northwest', year: 1959 },
- { label: 'Vertigo', year: 1958 },
- {
- label: 'Star Wars: Episode VI - Return of the Jedi',
- year: 1983,
- },
- { label: 'Reservoir Dogs', year: 1992 },
- { label: 'Braveheart', year: 1995 },
- { label: 'M', year: 1931 },
- { label: 'Requiem for a Dream', year: 2000 },
- { label: 'Amélie', year: 2001 },
- { label: 'A Clockwork Orange', year: 1971 },
- { label: 'Like Stars on Earth', year: 2007 },
- { label: 'Taxi Driver', year: 1976 },
- { label: 'Lawrence of Arabia', year: 1962 },
- { label: 'Double Indemnity', year: 1944 },
- {
- label: 'Eternal Sunshine of the Spotless Mind',
- year: 2004,
- },
- { label: 'Amadeus', year: 1984 },
- { label: 'To Kill a Mockingbird', year: 1962 },
- { label: 'Toy Story 3', year: 2010 },
- { label: 'Logan', year: 2017 },
- { label: 'Full Metal Jacket', year: 1987 },
- { label: 'Dangal', year: 2016 },
- { label: 'The Sting', year: 1973 },
- { label: '2001: A Space Odyssey', year: 1968 },
- { label: "Singin' in the Rain", year: 1952 },
- { label: 'Toy Story', year: 1995 },
- { label: 'Bicycle Thieves', year: 1948 },
- { label: 'The Kid', year: 1921 },
- { label: 'Inglourious Basterds', year: 2009 },
- { label: 'Snatch', year: 2000 },
- { label: '3 Idiots', year: 2009 },
- { label: 'Monty Python and the Holy Grail', year: 1975 },
-];
diff --git a/docs/data/base/components/autocomplete/UseAutocompletePopper.tsx.preview b/docs/data/base/components/autocomplete/UseAutocompletePopper.tsx.preview
deleted file mode 100644
index 1d9614ad6d..0000000000
--- a/docs/data/base/components/autocomplete/UseAutocompletePopper.tsx.preview
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/data/base/components/autocomplete/autocomplete.md b/docs/data/base/components/autocomplete/autocomplete.md
index 0bce06e3a9..bade0ca0ed 100644
--- a/docs/data/base/components/autocomplete/autocomplete.md
+++ b/docs/data/base/components/autocomplete/autocomplete.md
@@ -13,139 +13,3 @@ waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/
{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}
{{"component": "modules/components/ComponentPageTabs.js"}}
-
-## Introduction
-
-An autocomplete component is an enhanced text input that shows a list of suggested options as users type and lets them select an option from the list.
-
-Base UI provides the `useAutocomplete` hook for building a custom Autocomplete.
-It implements the WAI-ARIA Combobox pattern and is typically used to assist users in completing form inputs or search queries faster.
-
-{{"demo": "AutocompleteIntroduction", "defaultCodeOpen": false, "bg": "gradient"}}
-
-:::warning
-Material UI and Joy UI have Autocomplete components that are built using the `useAutocomplete` hook, and they include many features not yet described here.
-
-To learn more about implementing a custom Autocomplete, you can explore the [`useAutocomplete` API docs](/base-ui/react-autocomplete/hooks-api/#use-autocomplete), or reference the Material UI and Joy UI implementations:
-
-- [Material UI Autocomplete](https://mui.com/material-ui/react-autocomplete/)
-- [Joy UI Autocomplete](https://mui.com/joy-ui/react-autocomplete/)
-
-:::
-
-## Hook
-
-```jsx
-import { useAutocomplete } from '@base_ui/react/useAutocomplete';
-```
-
-The `useAutocomplete` hook requires a list of `options` to be displayed when the textbox receives focus.
-The value must be chosen from a predefined set of values.
-
-The following demo shows how to create a simple combobox, apply styles, and write the selected value to a state variable using the `onChange` prop:
-
-{{"demo": "UseAutocomplete.js"}}
-
-## Customization
-
-### Rendering options
-
-By default, the `options` prop accepts an array of `string`s or `{ label: string }`:
-
-```js
-const options = [
- { label: 'The Godfather', id: 1 },
- { label: 'Pulp Fiction', id: 2 },
-];
-// or
-const options = ['The Godfather', 'Pulp Fiction'];
-```
-
-If you need to use a different structure for options, you must provide a function to the `getOptionLabel` prop that resolves each option to a unique value.
-
-```js
-const options = [
- { issuer: 'Bank of America', brand: 'Visa', last4: '1234' },
- { issuer: 'Bank of America', brand: 'MasterCard', last4: '5678' },
- { issuer: 'Barclays', brand: 'Visa', last4: '4698' },
- // ...
-];
-
-const {
- getRootProps,
- // etc
-} = useAutocomplete({
- getOptionLabel: (option) => option.last4,
-});
-```
-
-### Controlled states
-
-The `useAutocomplete` hook has two states that can be controlled:
-
-1. the "value" state with the `value`/`onChange` props combination. This state represents the value selected by the user, for instance when pressing Enter .
-2. the "input value" state with the `inputValue`/`onInputChange` props combination. This state represents the value displayed in the textbox.
-
-These two states are isolated and should be controlled independently.
-
-:::info
-
-- A component is **controlled** when it's managed by its parent using props.
-- A component is **uncontrolled** when it's managed by its own local state.
-
-Learn more about controlled and uncontrolled components in the [React documentation](https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components).
-:::
-
-{{"demo": "ControlledStates.js"}}
-
-### Using a portal
-
-React Portals can be used to render the listbox outside of the DOM hierarchy, making it easier to allow it to "float" above adjacent elements.
-
-Base UI provides a [Popper](/base-ui/react-popper/) component built around React's `createPortal()` for exactly this purpose, and additionally helps you manage keyboard focus as it moves in and out of the portal.
-
-To render the listbox in Base UI's Popper, the `ref`s must be merged as follows:
-
-```jsx
-import { useAutocomplete } from '@base_ui/react/useAutocomplete';
-import { Popper } from '@base_ui/react/Popper';
-import { unstable_useForkRef as useForkRef } from '@mui/utils';
-
-export default function App(props) {
- const {
- getRootProps,
- getInputProps,
- getListboxProps,
- getOptionProps,
- popupOpen,
- anchorEl,
- setAnchorEl,
- groupedOptions,
- } = useAutocomplete(props);
-
- const rootRef = useForkRef(ref, setAnchorEl);
-
- return (
-
-
-
-
- {anchorEl && (
-
- {groupedOptions.length > 0 && (
-
- {groupedOptions.map((option, index) => (
- {option.label}
- ))}
-
- )}
-
- )}
-
- );
-}
-```
-
-Here's a complete demo that renders the listbox inside a Popper:
-
-{{"demo": "UseAutocompletePopper.js", "defaultCodeOpen": false}}
diff --git a/docs/data/base/components/badge/AccessibleBadges.js b/docs/data/base/components/badge/AccessibleBadges.js
deleted file mode 100644
index caedd2ef1e..0000000000
--- a/docs/data/base/components/badge/AccessibleBadges.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import * as React from 'react';
-import { styled } from '@mui/system';
-import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge';
-import MailIcon from '@mui/icons-material/Mail';
-
-function notificationsLabel(count) {
- if (count === 0) {
- return 'no notifications';
- }
- if (count > 99) {
- return 'more than 99 notifications';
- }
- return `${count} notifications`;
-}
-
-export default function AccessibleBadges() {
- return (
-
-
-
-
-
- );
-}
-const blue = {
- 500: '#007FFF',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Badge = styled(BaseBadge)(
- ({ theme }) => `
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- font-size: 14px;
- list-style: none;
- font-family: 'IBM Plex Sans', sans-serif;
- position: relative;
- display: inline-block;
- line-height: 1;
-
- & .${badgeClasses.badge} {
- z-index: auto;
- position: absolute;
- top: 0;
- right: 0;
- min-width: 22px;
- height: 22px;
- padding: 0 6px;
- color: #fff;
- font-weight: 600;
- font-size: 12px;
- line-height: 22px;
- white-space: nowrap;
- text-align: center;
- border-radius: 12px;
- background: ${blue[500]};
- box-shadow: 0px 4px 6x ${theme.palette.mode === 'dark' ? grey[900] : grey[300]};
- transform: translate(50%, -50%);
- transform-origin: 100% 0;
- }
- `,
-);
diff --git a/docs/data/base/components/badge/AccessibleBadges.tsx b/docs/data/base/components/badge/AccessibleBadges.tsx
deleted file mode 100644
index caf8866453..0000000000
--- a/docs/data/base/components/badge/AccessibleBadges.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import * as React from 'react';
-import { styled } from '@mui/system';
-import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge';
-import MailIcon from '@mui/icons-material/Mail';
-
-function notificationsLabel(count: number) {
- if (count === 0) {
- return 'no notifications';
- }
- if (count > 99) {
- return 'more than 99 notifications';
- }
- return `${count} notifications`;
-}
-
-export default function AccessibleBadges() {
- return (
-
-
-
-
-
- );
-}
-const blue = {
- 500: '#007FFF',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Badge = styled(BaseBadge)(
- ({ theme }) => `
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- font-size: 14px;
- list-style: none;
- font-family: 'IBM Plex Sans', sans-serif;
- position: relative;
- display: inline-block;
- line-height: 1;
-
- & .${badgeClasses.badge} {
- z-index: auto;
- position: absolute;
- top: 0;
- right: 0;
- min-width: 22px;
- height: 22px;
- padding: 0 6px;
- color: #fff;
- font-weight: 600;
- font-size: 12px;
- line-height: 22px;
- white-space: nowrap;
- text-align: center;
- border-radius: 12px;
- background: ${blue[500]};
- box-shadow: 0px 4px 6x ${theme.palette.mode === 'dark' ? grey[900] : grey[300]};
- transform: translate(50%, -50%);
- transform-origin: 100% 0;
- }
- `,
-);
diff --git a/docs/data/base/components/badge/AccessibleBadges.tsx.preview b/docs/data/base/components/badge/AccessibleBadges.tsx.preview
deleted file mode 100644
index 9fde77197a..0000000000
--- a/docs/data/base/components/badge/AccessibleBadges.tsx.preview
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/docs/data/base/components/badge/BadgeMax.js b/docs/data/base/components/badge/BadgeMax.js
deleted file mode 100644
index e883b2437a..0000000000
--- a/docs/data/base/components/badge/BadgeMax.js
+++ /dev/null
@@ -1,72 +0,0 @@
-import * as React from 'react';
-import Stack from '@mui/material/Stack';
-import { styled } from '@mui/system';
-import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge';
-import MailIcon from '@mui/icons-material/Mail';
-
-export default function BadgeMax() {
- return (
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-const blue = {
- 500: '#007FFF',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Badge = styled(BaseBadge)(
- ({ theme }) => `
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- font-size: 14px;
- list-style: none;
- font-family: 'IBM Plex Sans', sans-serif;
- position: relative;
- display: inline-block;
- line-height: 1;
-
- & .${badgeClasses.badge} {
- z-index: auto;
- position: absolute;
- top: 0;
- right: 0;
- min-width: 22px;
- height: 22px;
- padding: 0 6px;
- color: #fff;
- font-weight: 600;
- font-size: 12px;
- line-height: 22px;
- white-space: nowrap;
- text-align: center;
- border-radius: 12px;
- background: ${blue[500]};
- box-shadow: 0px 4px 6x ${theme.palette.mode === 'dark' ? grey[900] : grey[300]};
- transform: translate(50%, -50%);
- transform-origin: 100% 0;
- }
- `,
-);
diff --git a/docs/data/base/components/badge/BadgeMax.tsx b/docs/data/base/components/badge/BadgeMax.tsx
deleted file mode 100644
index e883b2437a..0000000000
--- a/docs/data/base/components/badge/BadgeMax.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import * as React from 'react';
-import Stack from '@mui/material/Stack';
-import { styled } from '@mui/system';
-import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge';
-import MailIcon from '@mui/icons-material/Mail';
-
-export default function BadgeMax() {
- return (
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-const blue = {
- 500: '#007FFF',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Badge = styled(BaseBadge)(
- ({ theme }) => `
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- font-size: 14px;
- list-style: none;
- font-family: 'IBM Plex Sans', sans-serif;
- position: relative;
- display: inline-block;
- line-height: 1;
-
- & .${badgeClasses.badge} {
- z-index: auto;
- position: absolute;
- top: 0;
- right: 0;
- min-width: 22px;
- height: 22px;
- padding: 0 6px;
- color: #fff;
- font-weight: 600;
- font-size: 12px;
- line-height: 22px;
- white-space: nowrap;
- text-align: center;
- border-radius: 12px;
- background: ${blue[500]};
- box-shadow: 0px 4px 6x ${theme.palette.mode === 'dark' ? grey[900] : grey[300]};
- transform: translate(50%, -50%);
- transform-origin: 100% 0;
- }
- `,
-);
diff --git a/docs/data/base/components/badge/BadgeMax.tsx.preview b/docs/data/base/components/badge/BadgeMax.tsx.preview
deleted file mode 100644
index a95263effa..0000000000
--- a/docs/data/base/components/badge/BadgeMax.tsx.preview
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/data/base/components/badge/BadgeVisibility.js b/docs/data/base/components/badge/BadgeVisibility.js
deleted file mode 100644
index 2aca401dd3..0000000000
--- a/docs/data/base/components/badge/BadgeVisibility.js
+++ /dev/null
@@ -1,223 +0,0 @@
-import * as React from 'react';
-import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge';
-// Auxiliary demo components
-import { styled, Stack } from '@mui/system';
-import { Button, buttonClasses } from '@base_ui/react/Button';
-import * as BaseSwitch from '@base_ui/react/Switch';
-import Divider from '@mui/material/Divider';
-// Icons
-import AddIcon from '@mui/icons-material/Add';
-import RemoveIcon from '@mui/icons-material/Remove';
-import MailIcon from '@mui/icons-material/Mail';
-
-const blue = {
- 200: '#99CCF3',
- 500: '#007FFF',
- 700: '#0059B2',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Badge = styled(BaseBadge)(
- ({ theme }) => `
- box-sizing: border-box;
- position: relative;
- display: flex;
- align-self: center;
- margin: 0;
- padding: 0;
- list-style: none;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 14px;
- line-height: 1;
-
- & .${badgeClasses.badge} {
- z-index: auto;
- position: absolute;
- top: 0;
- right: 0;
- min-width: 22px;
- height: 22px;
- padding: 0 6px;
- color: #fff;
- font-weight: 600;
- font-size: 12px;
- line-height: 22px;
- white-space: nowrap;
- text-align: center;
- border-radius: 12px;
- background: ${blue[500]};
- box-shadow: 0px 4px 6x ${theme.palette.mode === 'dark' ? grey[900] : grey[300]};
- transform: translate(50%, -50%);
- transform-origin: 100% 0;
- }
-
- & .${badgeClasses.invisible} {
- opacity: 0;
- pointer-events: none;
- }
- `,
-);
-
-const StyledButton = styled(Button)(
- ({ theme }) => `
- cursor: pointer;
- padding: 4px 8px;
- display: flex;
- align-items: center;
- border-radius: 8px;
- transition: all 150ms ease;
- background-color: transparent;
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[200]};
-
- &:hover {
- background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]};
- }
-
- &.${buttonClasses.active} {
- background: ${theme.palette.mode === 'dark' ? grey[900] : grey[100]};
- }
-
- &.${buttonClasses.focusVisible} {
- box-shadow: 0 3px 20px 0 rgba(61, 71, 82, 0.1), 0 0 0 4px rgba(0, 127, 255, 0.5);
- outline: none;
- }
- `,
-);
-
-const Switch = styled(BaseSwitch.Root)(
- ({ theme }) => `
- width: 32px;
- height: 20px;
- padding: 0;
- box-sizing: border-box;
- background: ${theme.palette.mode === 'dark' ? grey[600] : grey[400]};
- border-radius: 16px;
- border: none;
- display: inline-flex;
- transition-property: all;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-duration: 120ms;
- box-shadow: inset 0px 1px 1px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.05)'
- };
-
- &[data-disabled] {
- opacity: 0.4;
- cursor: not-allowed;
- }
-
- &:hover:not([data-disabled]) {
- background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
- border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]};
- }
-
- &:focus-visible {
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]};
- }
-
- &[data-state="checked"] {
- border: none;
- background: ${blue[500]};
- }
-
- &[data-state="checked"]:not([data-disabled]):hover {
- background: ${blue[700]};
- }
- `,
-);
-
-const Thumb = styled(BaseSwitch.Thumb)(
- ({ theme }) => `
- box-sizing: border-box;
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[200]};
- display: block;
- width: 14px;
- height: 14px;
- left: 3px;
- top: 3px;
- border-radius: 16px;
- background-color: #FFF;
- position: relative;
- transition-property: all;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-duration: 120ms;
- box-shadow: 0px 1px 2px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.25)' : 'rgba(0, 0, 0, 0.1)'
- };
-
- &[data-state="checked"] {
- left: 15px;
- background-color: #fff;
- box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3);
- }
-`,
-);
-
-const StyledLabel = styled('label')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- `,
-);
-
-export default function BadgeVisibility() {
- const [count, setCount] = React.useState(1);
- const [invisible, setInvisible] = React.useState(false);
-
- const handleBadgeVisibility = () => {
- setInvisible(!invisible);
- };
-
- return (
-
-
-
-
-
-
- {
- setCount(Math.max(count - 1, 0));
- }}
- >
-
-
- {
- setCount(count + 1);
- }}
- >
-
-
-
-
- Show badge
-
-
-
-
-
-
- );
-}
diff --git a/docs/data/base/components/badge/BadgeVisibility.tsx b/docs/data/base/components/badge/BadgeVisibility.tsx
deleted file mode 100644
index 2aca401dd3..0000000000
--- a/docs/data/base/components/badge/BadgeVisibility.tsx
+++ /dev/null
@@ -1,223 +0,0 @@
-import * as React from 'react';
-import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge';
-// Auxiliary demo components
-import { styled, Stack } from '@mui/system';
-import { Button, buttonClasses } from '@base_ui/react/Button';
-import * as BaseSwitch from '@base_ui/react/Switch';
-import Divider from '@mui/material/Divider';
-// Icons
-import AddIcon from '@mui/icons-material/Add';
-import RemoveIcon from '@mui/icons-material/Remove';
-import MailIcon from '@mui/icons-material/Mail';
-
-const blue = {
- 200: '#99CCF3',
- 500: '#007FFF',
- 700: '#0059B2',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Badge = styled(BaseBadge)(
- ({ theme }) => `
- box-sizing: border-box;
- position: relative;
- display: flex;
- align-self: center;
- margin: 0;
- padding: 0;
- list-style: none;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 14px;
- line-height: 1;
-
- & .${badgeClasses.badge} {
- z-index: auto;
- position: absolute;
- top: 0;
- right: 0;
- min-width: 22px;
- height: 22px;
- padding: 0 6px;
- color: #fff;
- font-weight: 600;
- font-size: 12px;
- line-height: 22px;
- white-space: nowrap;
- text-align: center;
- border-radius: 12px;
- background: ${blue[500]};
- box-shadow: 0px 4px 6x ${theme.palette.mode === 'dark' ? grey[900] : grey[300]};
- transform: translate(50%, -50%);
- transform-origin: 100% 0;
- }
-
- & .${badgeClasses.invisible} {
- opacity: 0;
- pointer-events: none;
- }
- `,
-);
-
-const StyledButton = styled(Button)(
- ({ theme }) => `
- cursor: pointer;
- padding: 4px 8px;
- display: flex;
- align-items: center;
- border-radius: 8px;
- transition: all 150ms ease;
- background-color: transparent;
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[200]};
-
- &:hover {
- background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]};
- }
-
- &.${buttonClasses.active} {
- background: ${theme.palette.mode === 'dark' ? grey[900] : grey[100]};
- }
-
- &.${buttonClasses.focusVisible} {
- box-shadow: 0 3px 20px 0 rgba(61, 71, 82, 0.1), 0 0 0 4px rgba(0, 127, 255, 0.5);
- outline: none;
- }
- `,
-);
-
-const Switch = styled(BaseSwitch.Root)(
- ({ theme }) => `
- width: 32px;
- height: 20px;
- padding: 0;
- box-sizing: border-box;
- background: ${theme.palette.mode === 'dark' ? grey[600] : grey[400]};
- border-radius: 16px;
- border: none;
- display: inline-flex;
- transition-property: all;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-duration: 120ms;
- box-shadow: inset 0px 1px 1px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.05)'
- };
-
- &[data-disabled] {
- opacity: 0.4;
- cursor: not-allowed;
- }
-
- &:hover:not([data-disabled]) {
- background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
- border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]};
- }
-
- &:focus-visible {
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]};
- }
-
- &[data-state="checked"] {
- border: none;
- background: ${blue[500]};
- }
-
- &[data-state="checked"]:not([data-disabled]):hover {
- background: ${blue[700]};
- }
- `,
-);
-
-const Thumb = styled(BaseSwitch.Thumb)(
- ({ theme }) => `
- box-sizing: border-box;
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[200]};
- display: block;
- width: 14px;
- height: 14px;
- left: 3px;
- top: 3px;
- border-radius: 16px;
- background-color: #FFF;
- position: relative;
- transition-property: all;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-duration: 120ms;
- box-shadow: 0px 1px 2px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.25)' : 'rgba(0, 0, 0, 0.1)'
- };
-
- &[data-state="checked"] {
- left: 15px;
- background-color: #fff;
- box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3);
- }
-`,
-);
-
-const StyledLabel = styled('label')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- `,
-);
-
-export default function BadgeVisibility() {
- const [count, setCount] = React.useState(1);
- const [invisible, setInvisible] = React.useState(false);
-
- const handleBadgeVisibility = () => {
- setInvisible(!invisible);
- };
-
- return (
-
-
-
-
-
-
- {
- setCount(Math.max(count - 1, 0));
- }}
- >
-
-
- {
- setCount(count + 1);
- }}
- >
-
-
-
-
- Show badge
-
-
-
-
-
-
- );
-}
diff --git a/docs/data/base/components/badge/ShowZeroBadge.js b/docs/data/base/components/badge/ShowZeroBadge.js
deleted file mode 100644
index 88616ba976..0000000000
--- a/docs/data/base/components/badge/ShowZeroBadge.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import * as React from 'react';
-import Stack from '@mui/material/Stack';
-import { styled } from '@mui/system';
-import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge';
-import MailIcon from '@mui/icons-material/Mail';
-
-export default function ShowZeroBadge() {
- return (
-
-
-
-
-
-
-
-
- );
-}
-
-const blue = {
- 500: '#007FFF',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Badge = styled(BaseBadge)(
- ({ theme }) => `
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- font-size: 14px;
- list-style: none;
- font-family: 'IBM Plex Sans', sans-serif;
- position: relative;
- display: inline-block;
- line-height: 1;
-
- & .${badgeClasses.badge} {
- z-index: auto;
- position: absolute;
- top: 0;
- right: 0;
- min-width: 22px;
- height: 22px;
- padding: 0 6px;
- color: #fff;
- font-weight: 600;
- font-size: 12px;
- line-height: 22px;
- white-space: nowrap;
- text-align: center;
- border-radius: 12px;
- background: ${blue[500]};
- box-shadow: 0px 4px 6x ${theme.palette.mode === 'dark' ? grey[900] : grey[300]};
- transform: translate(50%, -50%);
- transform-origin: 100% 0;
- }
-
- & .${badgeClasses.invisible} {
- display: none;
- }
- `,
-);
diff --git a/docs/data/base/components/badge/ShowZeroBadge.tsx b/docs/data/base/components/badge/ShowZeroBadge.tsx
deleted file mode 100644
index 88616ba976..0000000000
--- a/docs/data/base/components/badge/ShowZeroBadge.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import * as React from 'react';
-import Stack from '@mui/material/Stack';
-import { styled } from '@mui/system';
-import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge';
-import MailIcon from '@mui/icons-material/Mail';
-
-export default function ShowZeroBadge() {
- return (
-
-
-
-
-
-
-
-
- );
-}
-
-const blue = {
- 500: '#007FFF',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Badge = styled(BaseBadge)(
- ({ theme }) => `
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- font-size: 14px;
- list-style: none;
- font-family: 'IBM Plex Sans', sans-serif;
- position: relative;
- display: inline-block;
- line-height: 1;
-
- & .${badgeClasses.badge} {
- z-index: auto;
- position: absolute;
- top: 0;
- right: 0;
- min-width: 22px;
- height: 22px;
- padding: 0 6px;
- color: #fff;
- font-weight: 600;
- font-size: 12px;
- line-height: 22px;
- white-space: nowrap;
- text-align: center;
- border-radius: 12px;
- background: ${blue[500]};
- box-shadow: 0px 4px 6x ${theme.palette.mode === 'dark' ? grey[900] : grey[300]};
- transform: translate(50%, -50%);
- transform-origin: 100% 0;
- }
-
- & .${badgeClasses.invisible} {
- display: none;
- }
- `,
-);
diff --git a/docs/data/base/components/badge/ShowZeroBadge.tsx.preview b/docs/data/base/components/badge/ShowZeroBadge.tsx.preview
deleted file mode 100644
index 18283159fe..0000000000
--- a/docs/data/base/components/badge/ShowZeroBadge.tsx.preview
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/data/base/components/badge/UnstyledBadge/css/index.js b/docs/data/base/components/badge/UnstyledBadge/css/index.js
deleted file mode 100644
index e9f5d7f84f..0000000000
--- a/docs/data/base/components/badge/UnstyledBadge/css/index.js
+++ /dev/null
@@ -1,104 +0,0 @@
-import * as React from 'react';
-import { Badge } from '@base_ui/react/Badge';
-import { useTheme } from '@mui/system';
-
-export default function UnstyledBadge() {
- return (
-
-
-
-
-
-
- );
-}
-
-const cyan = {
- 50: '#E9F8FC',
- 100: '#BDEBF4',
- 200: '#99D8E5',
- 300: '#66BACC',
- 400: '#1F94AD',
- 500: '#0D5463',
- 600: '#094855',
- 700: '#063C47',
- 800: '#043039',
- 900: '#022127',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-function Styles() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
- return (
-
- );
-}
diff --git a/docs/data/base/components/badge/UnstyledBadge/css/index.tsx b/docs/data/base/components/badge/UnstyledBadge/css/index.tsx
deleted file mode 100644
index e9f5d7f84f..0000000000
--- a/docs/data/base/components/badge/UnstyledBadge/css/index.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import * as React from 'react';
-import { Badge } from '@base_ui/react/Badge';
-import { useTheme } from '@mui/system';
-
-export default function UnstyledBadge() {
- return (
-
-
-
-
-
-
- );
-}
-
-const cyan = {
- 50: '#E9F8FC',
- 100: '#BDEBF4',
- 200: '#99D8E5',
- 300: '#66BACC',
- 400: '#1F94AD',
- 500: '#0D5463',
- 600: '#094855',
- 700: '#063C47',
- 800: '#043039',
- 900: '#022127',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-function Styles() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
- return (
-
- );
-}
diff --git a/docs/data/base/components/badge/UnstyledBadge/css/index.tsx.preview b/docs/data/base/components/badge/UnstyledBadge/css/index.tsx.preview
deleted file mode 100644
index a1f344f2a6..0000000000
--- a/docs/data/base/components/badge/UnstyledBadge/css/index.tsx.preview
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/data/base/components/badge/UnstyledBadge/system/index.js b/docs/data/base/components/badge/UnstyledBadge/system/index.js
deleted file mode 100644
index 7787f48046..0000000000
--- a/docs/data/base/components/badge/UnstyledBadge/system/index.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import * as React from 'react';
-import { styled, Box } from '@mui/system';
-import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge';
-
-function BadgeContent() {
- return (
-
- theme.palette.mode === 'dark' ? grey[400] : grey[300],
- display: 'inline-block',
- verticalAlign: 'middle',
- }}
- />
- );
-}
-
-export default function UnstyledBadge() {
- return (
-
-
-
- );
-}
-
-const blue = {
- 500: '#007FFF',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Badge = styled(BaseBadge)(
- ({ theme }) => `
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- font-size: 14px;
- font-variant: tabular-nums;
- list-style: none;
- font-family: 'IBM Plex Sans', sans-serif;
- position: relative;
- display: inline-block;
- line-height: 1;
-
- & .${badgeClasses.badge} {
- z-index: auto;
- position: absolute;
- top: 0;
- right: 0;
- min-width: 22px;
- height: 22px;
- padding: 0 6px;
- color: #fff;
- font-weight: 600;
- font-size: 12px;
- line-height: 22px;
- white-space: nowrap;
- text-align: center;
- border-radius: 12px;
- background: ${blue[500]};
- box-shadow: 0px 4px 8px ${theme.palette.mode === 'dark' ? grey[900] : grey[300]};
- transform: translate(50%, -50%);
- transform-origin: 100% 0;
- }
- `,
-);
diff --git a/docs/data/base/components/badge/UnstyledBadge/system/index.tsx b/docs/data/base/components/badge/UnstyledBadge/system/index.tsx
deleted file mode 100644
index 7787f48046..0000000000
--- a/docs/data/base/components/badge/UnstyledBadge/system/index.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import * as React from 'react';
-import { styled, Box } from '@mui/system';
-import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge';
-
-function BadgeContent() {
- return (
-
- theme.palette.mode === 'dark' ? grey[400] : grey[300],
- display: 'inline-block',
- verticalAlign: 'middle',
- }}
- />
- );
-}
-
-export default function UnstyledBadge() {
- return (
-
-
-
- );
-}
-
-const blue = {
- 500: '#007FFF',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Badge = styled(BaseBadge)(
- ({ theme }) => `
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- font-size: 14px;
- font-variant: tabular-nums;
- list-style: none;
- font-family: 'IBM Plex Sans', sans-serif;
- position: relative;
- display: inline-block;
- line-height: 1;
-
- & .${badgeClasses.badge} {
- z-index: auto;
- position: absolute;
- top: 0;
- right: 0;
- min-width: 22px;
- height: 22px;
- padding: 0 6px;
- color: #fff;
- font-weight: 600;
- font-size: 12px;
- line-height: 22px;
- white-space: nowrap;
- text-align: center;
- border-radius: 12px;
- background: ${blue[500]};
- box-shadow: 0px 4px 8px ${theme.palette.mode === 'dark' ? grey[900] : grey[300]};
- transform: translate(50%, -50%);
- transform-origin: 100% 0;
- }
- `,
-);
diff --git a/docs/data/base/components/badge/UnstyledBadge/system/index.tsx.preview b/docs/data/base/components/badge/UnstyledBadge/system/index.tsx.preview
deleted file mode 100644
index 35b7141ca1..0000000000
--- a/docs/data/base/components/badge/UnstyledBadge/system/index.tsx.preview
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/docs/data/base/components/badge/UnstyledBadge/tailwind/index.js b/docs/data/base/components/badge/UnstyledBadge/tailwind/index.js
deleted file mode 100644
index d478d261bc..0000000000
--- a/docs/data/base/components/badge/UnstyledBadge/tailwind/index.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import clsx from 'clsx';
-import { Badge as BaseBadge } from '@base_ui/react/Badge';
-import { useTheme } from '@mui/system';
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-export default function UnstyledBadge() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
-
-
-
-
- );
-}
-
-const resolveSlotProps = (fn, args) => (typeof fn === 'function' ? fn(args) : fn);
-
-const Badge = React.forwardRef((props, ref) => {
- return (
- {
- const resolvedSlotProps = resolveSlotProps(
- props.slotProps?.root,
- ownerState,
- );
- return {
- ...resolvedSlotProps,
- className: clsx(
- 'box-border m-0 p-0 text-xs font-sans list-none relative inline-block leading-none',
- resolvedSlotProps?.className,
- ),
- };
- },
- badge: (ownerState) => {
- const resolvedSlotProps = resolveSlotProps(
- props.slotProps?.badge,
- ownerState,
- );
- return {
- ...resolvedSlotProps,
- className: clsx(
- 'z-auto absolute top-0 right-0 min-w-badge min-h-badge font-sans p-0 text-white font-semibold font-xs rounded-xl bg-purple-500 leading-5.5 whitespace-nowrap text-center translate-x-1/2 -translate-y-1/2 drop-shadow-lg origin-right',
- resolvedSlotProps?.className,
- ),
- };
- },
- }}
- />
- );
-});
-
-Badge.propTypes = {
- /**
- * The props used for each slot inside the Badge.
- * @default {}
- */
- slotProps: PropTypes.shape({
- badge: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
- root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
- }),
-};
diff --git a/docs/data/base/components/badge/UnstyledBadge/tailwind/index.tsx b/docs/data/base/components/badge/UnstyledBadge/tailwind/index.tsx
deleted file mode 100644
index 3d9bcd670b..0000000000
--- a/docs/data/base/components/badge/UnstyledBadge/tailwind/index.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import * as React from 'react';
-import clsx from 'clsx';
-import { Badge as BaseBadge, BadgeProps } from '@base_ui/react/Badge';
-import { useTheme } from '@mui/system';
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-export default function UnstyledBadge() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
-
-
-
-
- );
-}
-
-const resolveSlotProps = (fn: any, args: any) =>
- typeof fn === 'function' ? fn(args) : fn;
-
-const Badge = React.forwardRef((props, ref) => {
- return (
- {
- const resolvedSlotProps = resolveSlotProps(
- props.slotProps?.root,
- ownerState,
- );
- return {
- ...resolvedSlotProps,
- className: clsx(
- 'box-border m-0 p-0 text-xs font-sans list-none relative inline-block leading-none',
- resolvedSlotProps?.className,
- ),
- };
- },
- badge: (ownerState) => {
- const resolvedSlotProps = resolveSlotProps(
- props.slotProps?.badge,
- ownerState,
- );
- return {
- ...resolvedSlotProps,
- className: clsx(
- 'z-auto absolute top-0 right-0 min-w-badge min-h-badge font-sans p-0 text-white font-semibold font-xs rounded-xl bg-purple-500 leading-5.5 whitespace-nowrap text-center translate-x-1/2 -translate-y-1/2 drop-shadow-lg origin-right',
- resolvedSlotProps?.className,
- ),
- };
- },
- }}
- />
- );
-});
diff --git a/docs/data/base/components/badge/UnstyledBadge/tailwind/index.tsx.preview b/docs/data/base/components/badge/UnstyledBadge/tailwind/index.tsx.preview
deleted file mode 100644
index bd190ede6b..0000000000
--- a/docs/data/base/components/badge/UnstyledBadge/tailwind/index.tsx.preview
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.js b/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.js
deleted file mode 100644
index a27d5caf20..0000000000
--- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.js
+++ /dev/null
@@ -1,104 +0,0 @@
-import * as React from 'react';
-import { Badge } from '@base_ui/react/Badge';
-import { useTheme } from '@mui/system';
-
-export default function UnstyledBadgeIntroduction() {
- return (
-
-
-
-
-
-
- );
-}
-
-const cyan = {
- 50: '#E9F8FC',
- 100: '#BDEBF4',
- 200: '#99D8E5',
- 300: '#66BACC',
- 400: '#1F94AD',
- 500: '#0D5463',
- 600: '#094855',
- 700: '#063C47',
- 800: '#043039',
- 900: '#022127',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-function Styles() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
- return (
-
- );
-}
diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.tsx b/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.tsx
deleted file mode 100644
index a27d5caf20..0000000000
--- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import * as React from 'react';
-import { Badge } from '@base_ui/react/Badge';
-import { useTheme } from '@mui/system';
-
-export default function UnstyledBadgeIntroduction() {
- return (
-
-
-
-
-
-
- );
-}
-
-const cyan = {
- 50: '#E9F8FC',
- 100: '#BDEBF4',
- 200: '#99D8E5',
- 300: '#66BACC',
- 400: '#1F94AD',
- 500: '#0D5463',
- 600: '#094855',
- 700: '#063C47',
- 800: '#043039',
- 900: '#022127',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-function Styles() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
- return (
-
- );
-}
diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.tsx.preview b/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.tsx.preview
deleted file mode 100644
index 4b5d65f54f..0000000000
--- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.tsx.preview
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.js b/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.js
deleted file mode 100644
index e8998cecda..0000000000
--- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import * as React from 'react';
-import { styled, Box } from '@mui/system';
-import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge';
-
-const blue = {
- 100: '#DAECFF',
- 500: '#007FFF',
- 900: '#003A75',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function BadgeContent() {
- return (
-
- theme.palette.mode === 'dark' ? grey[700] : grey[200],
- display: 'inline-block',
- verticalAlign: 'middle',
- }}
- />
- );
-}
-
-export default function UnstyledBadgeIntroduction() {
- return (
-
-
-
- );
-}
-
-const Badge = styled(BaseBadge)(
- ({ theme }) => `
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- font-size: 14px;
- font-variant: tabular-nums;
- list-style: none;
- font-family: 'IBM Plex Sans', sans-serif;
- position: relative;
- display: inline-block;
- line-height: 1;
-
- & .${badgeClasses.badge} {
- z-index: auto;
- position: absolute;
- top: 0;
- right: 0;
- min-width: 22px;
- height: 22px;
- padding: 0 6px;
- color: #fff;
- font-weight: 600;
- font-size: 12px;
- line-height: 22px;
- white-space: nowrap;
- text-align: center;
- border-radius: 12px;
- background: ${blue[500]};
- box-shadow: 0px 2px 24px ${
- theme.palette.mode === 'dark' ? blue[900] : blue[100]
- };
- transform: translate(50%, -50%);
- transform-origin: 100% 0;
- }
- `,
-);
diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.tsx b/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.tsx
deleted file mode 100644
index e8998cecda..0000000000
--- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import * as React from 'react';
-import { styled, Box } from '@mui/system';
-import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge';
-
-const blue = {
- 100: '#DAECFF',
- 500: '#007FFF',
- 900: '#003A75',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function BadgeContent() {
- return (
-
- theme.palette.mode === 'dark' ? grey[700] : grey[200],
- display: 'inline-block',
- verticalAlign: 'middle',
- }}
- />
- );
-}
-
-export default function UnstyledBadgeIntroduction() {
- return (
-
-
-
- );
-}
-
-const Badge = styled(BaseBadge)(
- ({ theme }) => `
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- font-size: 14px;
- font-variant: tabular-nums;
- list-style: none;
- font-family: 'IBM Plex Sans', sans-serif;
- position: relative;
- display: inline-block;
- line-height: 1;
-
- & .${badgeClasses.badge} {
- z-index: auto;
- position: absolute;
- top: 0;
- right: 0;
- min-width: 22px;
- height: 22px;
- padding: 0 6px;
- color: #fff;
- font-weight: 600;
- font-size: 12px;
- line-height: 22px;
- white-space: nowrap;
- text-align: center;
- border-radius: 12px;
- background: ${blue[500]};
- box-shadow: 0px 2px 24px ${
- theme.palette.mode === 'dark' ? blue[900] : blue[100]
- };
- transform: translate(50%, -50%);
- transform-origin: 100% 0;
- }
- `,
-);
diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.tsx.preview b/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.tsx.preview
deleted file mode 100644
index 35b7141ca1..0000000000
--- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.tsx.preview
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.js b/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.js
deleted file mode 100644
index 2c6377cf2f..0000000000
--- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import clsx from 'clsx';
-import { Badge as BaseBadge } from '@base_ui/react/Badge';
-import { useTheme } from '@mui/system';
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-export default function UnstyledBadgeIntroduction() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
-
-
-
-
- );
-}
-
-const resolveSlotProps = (fn, args) => (typeof fn === 'function' ? fn(args) : fn);
-
-const Badge = React.forwardRef((props, ref) => {
- return (
- {
- const resolvedSlotProps = resolveSlotProps(
- props.slotProps?.root,
- ownerState,
- );
- return {
- ...resolvedSlotProps,
- className: clsx(
- 'box-border m-0 p-0 text-xs list-none relative inline-block leading-none',
- resolvedSlotProps?.className,
- ),
- };
- },
- badge: (ownerState) => {
- const resolvedSlotProps = resolveSlotProps(
- props.slotProps?.badge,
- ownerState,
- );
- return {
- ...resolvedSlotProps,
- className: clsx(
- 'z-auto absolute top-0 right-0 min-w-badge min-h-badge font-sans p-0 text-white font-semibold font-xs font-sans rounded-xl bg-purple-500 leading-5.5 whitespace-nowrap text-center translate-x-1/2 -translate-y-1/2 drop-shadow-lg origin-right',
- resolvedSlotProps?.className,
- ),
- };
- },
- }}
- />
- );
-});
-
-Badge.propTypes = {
- /**
- * The props used for each slot inside the Badge.
- * @default {}
- */
- slotProps: PropTypes.shape({
- badge: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
- root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
- }),
-};
diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.tsx b/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.tsx
deleted file mode 100644
index 5134735353..0000000000
--- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import * as React from 'react';
-import clsx from 'clsx';
-import { Badge as BaseBadge, BadgeProps } from '@base_ui/react/Badge';
-import { useTheme } from '@mui/system';
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-export default function UnstyledBadgeIntroduction() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
-
-
-
-
- );
-}
-
-const resolveSlotProps = (fn: any, args: any) =>
- typeof fn === 'function' ? fn(args) : fn;
-
-const Badge = React.forwardRef((props, ref) => {
- return (
- {
- const resolvedSlotProps = resolveSlotProps(
- props.slotProps?.root,
- ownerState,
- );
- return {
- ...resolvedSlotProps,
- className: clsx(
- 'box-border m-0 p-0 text-xs list-none relative inline-block leading-none',
- resolvedSlotProps?.className,
- ),
- };
- },
- badge: (ownerState) => {
- const resolvedSlotProps = resolveSlotProps(
- props.slotProps?.badge,
- ownerState,
- );
- return {
- ...resolvedSlotProps,
- className: clsx(
- 'z-auto absolute top-0 right-0 min-w-badge min-h-badge font-sans p-0 text-white font-semibold font-xs font-sans rounded-xl bg-purple-500 leading-5.5 whitespace-nowrap text-center translate-x-1/2 -translate-y-1/2 drop-shadow-lg origin-right',
- resolvedSlotProps?.className,
- ),
- };
- },
- }}
- />
- );
-});
diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.tsx.preview b/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.tsx.preview
deleted file mode 100644
index bd190ede6b..0000000000
--- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.tsx.preview
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/docs/data/base/components/badge/badge.md b/docs/data/base/components/badge/badge.md
deleted file mode 100644
index fc9e7139eb..0000000000
--- a/docs/data/base/components/badge/badge.md
+++ /dev/null
@@ -1,144 +0,0 @@
----
-productId: base-ui
-title: React Badge component and hook
-components: Badge
-hooks: useBadge
-githubLabel: 'component: badge'
----
-
-# Badge
-
-The Badge component generates a small label that is attached to its child element.
-
-{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}
-
-{{"component": "modules/components/ComponentPageTabs.js"}}
-
-## Introduction
-
-A badge is a small descriptor for UI elements.
-It typically sits on or near an element and indicates the status of that element by displaying a number, icon, or other short set of characters.
-
-The Badge component creates a badge that is applied to its child element.
-
-{{"demo": "UnstyledBadgeIntroduction", "defaultCodeOpen": false, "bg": "gradient"}}
-
-## Component
-
-```jsx
-import { Badge } from '@base_ui/react/Badge';
-```
-
-The Badge wraps around the UI element that it's attached to.
-
-### Anatomy
-
-The Badge component is composed of a root `` that houses the element that the Badge is attached to, followed by a `` slot to represent the Badge itself:
-
-```html
-
-
- badge content
-
-```
-
-### Custom structure
-
-Use the `slots` prop to override the root or any other interior slot:
-
-```jsx
-
-```
-
-:::info
-The `slots` prop is available on all non-utility Base components.
-See [Overriding component structure](/base-ui/guides/overriding-component-structure/) for full details.
-:::
-
-Use the `slotProps` prop to pass custom props to internal slots.
-The following code snippet applies a CSS class called `my-badge` to the badge slot:
-
-```jsx
-
-```
-
-### Usage with TypeScript
-
-In TypeScript, you can specify the custom component type used in the `slots.root` as a generic parameter of the unstyled component.
-This way, you can safely provide the custom root's props directly on the component:
-
-```tsx
- slots={{ root: CustomComponent }} customProp />
-```
-
-The same applies for props specific to custom primitive elements:
-
-```tsx
- slots={{ root: 'img' }} src="badge.png" />
-```
-
-## Hook
-
-```jsx
-import { useBadge } from '@base_ui/react/useBadge';
-```
-
-The `useBadge` hook lets you apply the functionality of a Badge to a fully custom component.
-It returns props to be placed on the custom component, along with fields representing the component's internal state.
-
-Hooks _do not_ support [slot props](#custom-structure), but they do support [customization props](#customization).
-
-:::info
-Hooks give you the most room for customization, but require more work to implement.
-With hooks, you can take full control over how your component is rendered, and define all the custom props and CSS classes you need.
-
-You may not need to use hooks unless you find that you're limited by the customization options of their component counterparts—for instance, if your component requires significantly different [structure](#anatomy).
-:::
-
-## Customization
-
-:::info
-The following features can be used with both components and hooks.
-For the sake of simplicity, demos, and code snippets primarily feature components.
-:::
-
-### Badge content
-
-The `badgeContent` prop defines the content that's displayed inside the Badge.
-When this content is a number, there are additional props you can use for further customization—see the [Numerical Badges section](#numerical-badges) below.
-
-The following demo shows how to create and style a typical numerical Badge that's attached to a generic box element:
-
-{{"demo": "UnstyledBadge", "defaultCodeOpen": false}}
-
-### Badge visibility
-
-You can control the visibility of a Badge by using the `invisible` prop.
-Setting a Badge to `invisible` does not actually hide it—instead, this prop adds the `BaseBadge-invisible` class to the Badge, which you can target with styles to hide however you prefer:
-
-{{"demo": "BadgeVisibility.js"}}
-
-### Numerical Badges
-
-The following props are useful when `badgeContent` is a number.
-
-#### The showZero prop
-
-By default, Badges automatically hide when `badgeContent={0}`.
-You can override this behavior with the `showZero` prop:
-
-{{"demo": "ShowZeroBadge.js"}}
-
-#### The max prop
-
-You can use the `max` prop to set a maximum value for `badgeContent`.
-The default is 99.
-
-{{"demo": "BadgeMax.js"}}
-
-## Accessibility
-
-Screen readers may not provide users with enough information about a Badge's contents.
-To make your badge accessible, you must provide a full description with `aria-label`, as shown in the demo below:
-
-{{"demo": "AccessibleBadges.js"}}
diff --git a/docs/data/base/components/button/UnstyledButtonCustom.js b/docs/data/base/components/button/UnstyledButtonCustom.js
deleted file mode 100644
index 225eeda467..0000000000
--- a/docs/data/base/components/button/UnstyledButtonCustom.js
+++ /dev/null
@@ -1,116 +0,0 @@
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import { Button, buttonClasses } from '@base_ui/react/Button';
-import { styled } from '@mui/system';
-
-const ButtonRoot = React.forwardRef(function ButtonRoot(props, ref) {
- const { children, ...other } = props;
-
- return (
-
-
-
-
- {children}
-
-
- );
-});
-
-ButtonRoot.propTypes = {
- children: PropTypes.node,
-};
-
-const SvgButton = React.forwardRef(function SvgButton(props, ref) {
- return ;
-});
-
-export default function UnstyledButtonCustom() {
- return Button ;
-}
-
-const blue = {
- 50: '#F0F7FF',
- 100: '#C2E0FF',
- 200: '#99CCF3',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E6',
- 700: '#0059B3',
- 800: '#004C99',
- 900: '#003A75',
-};
-
-const CustomButtonRoot = styled(ButtonRoot)(
- ({ theme }) => `
- overflow: visible;
- cursor: pointer;
- --main-color: ${theme.palette.mode === 'light' ? blue[600] : blue[200]};
- --hover-color: ${theme.palette.mode === 'light' ? blue[50] : blue[900]};
- --active-color: ${theme.palette.mode === 'light' ? blue[100] : blue[800]};
-
- & polygon {
- fill: transparent;
- transition: all 800ms ease;
- pointer-events: none;
- }
-
- & .bg {
- stroke: var(--main-color);
- stroke-width: 1;
- filter: drop-shadow(0 4px 16px rgba(0, 0, 0, 0.1));
- fill: transparent;
- }
-
- & .borderEffect {
- stroke: var(--main-color);
- stroke-width: 2;
- stroke-dasharray: 120 600;
- stroke-dashoffset: 120;
- fill: transparent;
- }
-
- &:hover,
- &.${buttonClasses.focusVisible} {
- .borderEffect {
- stroke-dashoffset: -600;
- }
-
- .bg {
- fill: var(--hover-color);
- }
- }
-
- &:focus,
- &.${buttonClasses.focusVisible} {
- outline: 2px solid ${theme.palette.mode === 'dark' ? blue[700] : blue[200]};
- outline-offset: 2px;
- }
-
- &.${buttonClasses.active} {
- & .bg {
- fill: var(--active-color);
- transition: fill 150ms ease-out;
- }
- }
-
- & foreignObject {
- pointer-events: none;
-
- & .content {
- font-size: 0.875rem;
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 600;
- line-height: 1.5;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--main-color);
- }
-
- & svg {
- margin: 0 4px;
- }
- }`,
-);
diff --git a/docs/data/base/components/button/UnstyledButtonCustom.tsx b/docs/data/base/components/button/UnstyledButtonCustom.tsx
deleted file mode 100644
index 3df425acad..0000000000
--- a/docs/data/base/components/button/UnstyledButtonCustom.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import * as React from 'react';
-import { Button, ButtonProps, buttonClasses } from '@base_ui/react/Button';
-import { styled, Theme } from '@mui/system';
-
-const ButtonRoot = React.forwardRef(function ButtonRoot(
- props: React.PropsWithChildren<{}>,
- ref: React.ForwardedRef,
-) {
- const { children, ...other } = props;
-
- return (
-
-
-
-
- {children}
-
-
- );
-});
-
-const SvgButton = React.forwardRef(function SvgButton(
- props: ButtonProps,
- ref: React.ForwardedRef,
-) {
- return ;
-});
-
-export default function UnstyledButtonCustom() {
- return Button ;
-}
-
-const blue = {
- 50: '#F0F7FF',
- 100: '#C2E0FF',
- 200: '#99CCF3',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E6',
- 700: '#0059B3',
- 800: '#004C99',
- 900: '#003A75',
-};
-
-const CustomButtonRoot = styled(ButtonRoot)(
- ({ theme }: { theme: Theme }) => `
- overflow: visible;
- cursor: pointer;
- --main-color: ${theme.palette.mode === 'light' ? blue[600] : blue[200]};
- --hover-color: ${theme.palette.mode === 'light' ? blue[50] : blue[900]};
- --active-color: ${theme.palette.mode === 'light' ? blue[100] : blue[800]};
-
- & polygon {
- fill: transparent;
- transition: all 800ms ease;
- pointer-events: none;
- }
-
- & .bg {
- stroke: var(--main-color);
- stroke-width: 1;
- filter: drop-shadow(0 4px 16px rgba(0, 0, 0, 0.1));
- fill: transparent;
- }
-
- & .borderEffect {
- stroke: var(--main-color);
- stroke-width: 2;
- stroke-dasharray: 120 600;
- stroke-dashoffset: 120;
- fill: transparent;
- }
-
- &:hover,
- &.${buttonClasses.focusVisible} {
- .borderEffect {
- stroke-dashoffset: -600;
- }
-
- .bg {
- fill: var(--hover-color);
- }
- }
-
- &:focus,
- &.${buttonClasses.focusVisible} {
- outline: 2px solid ${theme.palette.mode === 'dark' ? blue[700] : blue[200]};
- outline-offset: 2px;
- }
-
- &.${buttonClasses.active} {
- & .bg {
- fill: var(--active-color);
- transition: fill 150ms ease-out;
- }
- }
-
- & foreignObject {
- pointer-events: none;
-
- & .content {
- font-size: 0.875rem;
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 600;
- line-height: 1.5;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--main-color);
- }
-
- & svg {
- margin: 0 4px;
- }
- }`,
-);
diff --git a/docs/data/base/components/button/UnstyledButtonCustom.tsx.preview b/docs/data/base/components/button/UnstyledButtonCustom.tsx.preview
deleted file mode 100644
index 886d324a1f..0000000000
--- a/docs/data/base/components/button/UnstyledButtonCustom.tsx.preview
+++ /dev/null
@@ -1 +0,0 @@
-Button
\ No newline at end of file
diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/css/index.js b/docs/data/base/components/button/UnstyledButtonIntroduction/css/index.js
deleted file mode 100644
index b36ce05b76..0000000000
--- a/docs/data/base/components/button/UnstyledButtonIntroduction/css/index.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import * as React from 'react';
-import { Button } from '@base_ui/react/Button';
-import { useTheme } from '@mui/system';
-import Stack from '@mui/material/Stack';
-
-export default function UnstyledButtonsIntroduction() {
- return (
-
-
- Button
-
- Disabled
-
-
-
-
- );
-}
-
-const cyan = {
- 50: '#E9F8FC',
- 100: '#BDEBF4',
- 200: '#99D8E5',
- 300: '#66BACC',
- 400: '#1F94AD',
- 500: '#0D5463',
- 600: '#094855',
- 700: '#063C47',
- 800: '#043039',
- 900: '#022127',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-function Styles() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
- );
-}
diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/css/index.tsx b/docs/data/base/components/button/UnstyledButtonIntroduction/css/index.tsx
deleted file mode 100644
index b36ce05b76..0000000000
--- a/docs/data/base/components/button/UnstyledButtonIntroduction/css/index.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import * as React from 'react';
-import { Button } from '@base_ui/react/Button';
-import { useTheme } from '@mui/system';
-import Stack from '@mui/material/Stack';
-
-export default function UnstyledButtonsIntroduction() {
- return (
-
-
- Button
-
- Disabled
-
-
-
-
- );
-}
-
-const cyan = {
- 50: '#E9F8FC',
- 100: '#BDEBF4',
- 200: '#99D8E5',
- 300: '#66BACC',
- 400: '#1F94AD',
- 500: '#0D5463',
- 600: '#094855',
- 700: '#063C47',
- 800: '#043039',
- 900: '#022127',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-function Styles() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
- );
-}
diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/css/index.tsx.preview b/docs/data/base/components/button/UnstyledButtonIntroduction/css/index.tsx.preview
deleted file mode 100644
index a7acca3d14..0000000000
--- a/docs/data/base/components/button/UnstyledButtonIntroduction/css/index.tsx.preview
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
- Button
-
- Disabled
-
-
-
-
\ No newline at end of file
diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.js b/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.js
deleted file mode 100644
index 3a17beb8a2..0000000000
--- a/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import * as React from 'react';
-import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button';
-import { styled } from '@mui/system';
-import Stack from '@mui/material/Stack';
-
-export default function UnstyledButtonsIntroduction() {
- return (
-
- Button
- Disabled
-
- );
-}
-
-const blue = {
- 200: '#99CCFF',
- 300: '#66B2FF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0066CC',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Button = styled(BaseButton)(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 600;
- font-size: 0.875rem;
- line-height: 1.5;
- background-color: ${blue[500]};
- padding: 8px 16px;
- border-radius: 8px;
- color: white;
- transition: all 150ms ease;
- cursor: pointer;
- border: 1px solid ${blue[500]};
- box-shadow: 0 2px 1px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)'
- }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]};
-
- &:hover {
- background-color: ${blue[600]};
- }
-
- &.${buttonClasses.active} {
- background-color: ${blue[700]};
- box-shadow: none;
- transform: scale(0.99);
- }
-
- &.${buttonClasses.focusVisible} {
- box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
- outline: none;
- }
-
- &.${buttonClasses.disabled} {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
- border: 0;
- cursor: default;
- box-shadow: none;
- transform: scale(1);
- }
- `,
-);
diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.tsx b/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.tsx
deleted file mode 100644
index 3a17beb8a2..0000000000
--- a/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import * as React from 'react';
-import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button';
-import { styled } from '@mui/system';
-import Stack from '@mui/material/Stack';
-
-export default function UnstyledButtonsIntroduction() {
- return (
-
- Button
- Disabled
-
- );
-}
-
-const blue = {
- 200: '#99CCFF',
- 300: '#66B2FF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0066CC',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Button = styled(BaseButton)(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 600;
- font-size: 0.875rem;
- line-height: 1.5;
- background-color: ${blue[500]};
- padding: 8px 16px;
- border-radius: 8px;
- color: white;
- transition: all 150ms ease;
- cursor: pointer;
- border: 1px solid ${blue[500]};
- box-shadow: 0 2px 1px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)'
- }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]};
-
- &:hover {
- background-color: ${blue[600]};
- }
-
- &.${buttonClasses.active} {
- background-color: ${blue[700]};
- box-shadow: none;
- transform: scale(0.99);
- }
-
- &.${buttonClasses.focusVisible} {
- box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
- outline: none;
- }
-
- &.${buttonClasses.disabled} {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
- border: 0;
- cursor: default;
- box-shadow: none;
- transform: scale(1);
- }
- `,
-);
diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.tsx.preview b/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.tsx.preview
deleted file mode 100644
index b7b5add52a..0000000000
--- a/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.tsx.preview
+++ /dev/null
@@ -1,2 +0,0 @@
-Button
-Disabled
\ No newline at end of file
diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.js b/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.js
deleted file mode 100644
index 1af53ada6b..0000000000
--- a/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import { Button as BaseButton } from '@base_ui/react/Button';
-import Stack from '@mui/material/Stack';
-import clsx from 'clsx';
-import { useTheme } from '@mui/system';
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-export default function UnstyledButtonsIntroduction() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
-
- Button
- Disabled
-
-
- );
-}
-
-const CustomButton = React.forwardRef((props, ref) => {
- const { className, ...other } = props;
- return (
-
- );
-});
-
-CustomButton.propTypes = {
- className: PropTypes.string,
-};
diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.tsx b/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.tsx
deleted file mode 100644
index a8b9ee179f..0000000000
--- a/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import * as React from 'react';
-import { Button as BaseButton, ButtonProps } from '@base_ui/react/Button';
-import Stack from '@mui/material/Stack';
-import clsx from 'clsx';
-import { useTheme } from '@mui/system';
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-export default function UnstyledButtonsIntroduction() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
-
- Button
- Disabled
-
-
- );
-}
-
-const CustomButton = React.forwardRef(
- (props, ref) => {
- const { className, ...other } = props;
- return (
-
- );
- },
-);
diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.tsx.preview b/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.tsx.preview
deleted file mode 100644
index 53f6465d2c..0000000000
--- a/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.tsx.preview
+++ /dev/null
@@ -1,4 +0,0 @@
-
- Button
- Disabled
-
\ No newline at end of file
diff --git a/docs/data/base/components/button/UnstyledButtonsDisabledFocus.js b/docs/data/base/components/button/UnstyledButtonsDisabledFocus.js
deleted file mode 100644
index db4f511e8f..0000000000
--- a/docs/data/base/components/button/UnstyledButtonsDisabledFocus.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import * as React from 'react';
-import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button';
-import { styled } from '@mui/system';
-import Stack from '@mui/material/Stack';
-
-export default function UnstyledButtonsDisabledFocus() {
- return (
-
- {'focusableWhenDisabled = false'}
-
- {'focusableWhenDisabled = true'}
-
-
- );
-}
-
-const blue = {
- 200: '#99CCFF',
- 300: '#66B2FF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0066CC',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Button = styled(BaseButton)(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 600;
- font-size: 0.875rem;
- line-height: 1.5;
- background-color: ${blue[500]};
- padding: 8px 16px;
- border-radius: 8px;
- color: white;
- transition: all 150ms ease;
- cursor: pointer;
- border: 1px solid ${blue[500]};
- box-shadow: 0 2px 1px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)'
- }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]};
-
- &:hover {
- background-color: ${blue[600]};
- }
-
- &.${buttonClasses.active} {
- background-color: ${blue[700]};
- box-shadow: none;
- transform: scale(0.99);
- }
-
- &.${buttonClasses.focusVisible} {
- box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
- outline: none;
- }
-
- &.${buttonClasses.disabled} {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
- border: 0;
- cursor: default;
- box-shadow: none;
- transform: scale(1);
- }
-`,
-);
diff --git a/docs/data/base/components/button/UnstyledButtonsDisabledFocus.tsx b/docs/data/base/components/button/UnstyledButtonsDisabledFocus.tsx
deleted file mode 100644
index db4f511e8f..0000000000
--- a/docs/data/base/components/button/UnstyledButtonsDisabledFocus.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import * as React from 'react';
-import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button';
-import { styled } from '@mui/system';
-import Stack from '@mui/material/Stack';
-
-export default function UnstyledButtonsDisabledFocus() {
- return (
-
- {'focusableWhenDisabled = false'}
-
- {'focusableWhenDisabled = true'}
-
-
- );
-}
-
-const blue = {
- 200: '#99CCFF',
- 300: '#66B2FF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0066CC',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Button = styled(BaseButton)(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 600;
- font-size: 0.875rem;
- line-height: 1.5;
- background-color: ${blue[500]};
- padding: 8px 16px;
- border-radius: 8px;
- color: white;
- transition: all 150ms ease;
- cursor: pointer;
- border: 1px solid ${blue[500]};
- box-shadow: 0 2px 1px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)'
- }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]};
-
- &:hover {
- background-color: ${blue[600]};
- }
-
- &.${buttonClasses.active} {
- background-color: ${blue[700]};
- box-shadow: none;
- transform: scale(0.99);
- }
-
- &.${buttonClasses.focusVisible} {
- box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
- outline: none;
- }
-
- &.${buttonClasses.disabled} {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
- border: 0;
- cursor: default;
- box-shadow: none;
- transform: scale(1);
- }
-`,
-);
diff --git a/docs/data/base/components/button/UnstyledButtonsDisabledFocus.tsx.preview b/docs/data/base/components/button/UnstyledButtonsDisabledFocus.tsx.preview
deleted file mode 100644
index 13ae2ccca2..0000000000
--- a/docs/data/base/components/button/UnstyledButtonsDisabledFocus.tsx.preview
+++ /dev/null
@@ -1,4 +0,0 @@
-{'focusableWhenDisabled = false'}
-
- {'focusableWhenDisabled = true'}
-
\ No newline at end of file
diff --git a/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.js b/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.js
deleted file mode 100644
index 9c39abc808..0000000000
--- a/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.js
+++ /dev/null
@@ -1,82 +0,0 @@
-import * as React from 'react';
-import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button';
-import { styled } from '@mui/system';
-import Stack from '@mui/material/Stack';
-
-export default function UnstyledButtonsDisabledFocusCustom() {
- return (
-
-
- {'focusableWhenDisabled = false'}
-
-
- {'focusableWhenDisabled = true'}
-
-
- );
-}
-
-const blue = {
- 200: '#99CCFF',
- 300: '#66B2FF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0066CC',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Button = styled(BaseButton)(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 600;
- font-size: 0.875rem;
- line-height: 1.5;
- background-color: ${blue[500]};
- padding: 8px 16px;
- border-radius: 8px;
- color: white;
- transition: all 150ms ease;
- cursor: pointer;
- border: 1px solid ${blue[500]};
- box-shadow: 0 2px 1px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)'
- }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]};
-
- &:hover {
- background-color: ${blue[600]};
- }
-
- &.${buttonClasses.active} {
- background-color: ${blue[700]};
- box-shadow: none;
- transform: scale(0.99);
- }
-
- &.${buttonClasses.focusVisible} {
- box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
- outline: none;
- }
-
- &.${buttonClasses.disabled} {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
- border: 0;
- cursor: default;
- box-shadow: none;
- transform: scale(1);
- }
-`,
-);
diff --git a/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.tsx b/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.tsx
deleted file mode 100644
index c12b05e26c..0000000000
--- a/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import * as React from 'react';
-import {
- Button as BaseButton,
- buttonClasses,
- ButtonTypeMap,
-} from '@base_ui/react/Button';
-import { styled } from '@mui/system';
-import Stack from '@mui/material/Stack';
-import { PolymorphicComponent } from '@base_ui/react/utils';
-
-export default function UnstyledButtonsDisabledFocusCustom() {
- return (
-
-
- {'focusableWhenDisabled = false'}
-
-
- {'focusableWhenDisabled = true'}
-
-
- );
-}
-
-const blue = {
- 200: '#99CCFF',
- 300: '#66B2FF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0066CC',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Button = styled(BaseButton)(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 600;
- font-size: 0.875rem;
- line-height: 1.5;
- background-color: ${blue[500]};
- padding: 8px 16px;
- border-radius: 8px;
- color: white;
- transition: all 150ms ease;
- cursor: pointer;
- border: 1px solid ${blue[500]};
- box-shadow: 0 2px 1px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)'
- }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]};
-
- &:hover {
- background-color: ${blue[600]};
- }
-
- &.${buttonClasses.active} {
- background-color: ${blue[700]};
- box-shadow: none;
- transform: scale(0.99);
- }
-
- &.${buttonClasses.focusVisible} {
- box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
- outline: none;
- }
-
- &.${buttonClasses.disabled} {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
- border: 0;
- cursor: default;
- box-shadow: none;
- transform: scale(1);
- }
-`,
-) as PolymorphicComponent;
diff --git a/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.tsx.preview b/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.tsx.preview
deleted file mode 100644
index 61742c2ce4..0000000000
--- a/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.tsx.preview
+++ /dev/null
@@ -1,6 +0,0 @@
-
- {'focusableWhenDisabled = false'}
-
-
- {'focusableWhenDisabled = true'}
-
\ No newline at end of file
diff --git a/docs/data/base/components/button/UnstyledButtonsSimple.js b/docs/data/base/components/button/UnstyledButtonsSimple.js
deleted file mode 100644
index 28c2022603..0000000000
--- a/docs/data/base/components/button/UnstyledButtonsSimple.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import * as React from 'react';
-import { Button as BaseButton } from '@base_ui/react/Button';
-import { styled } from '@mui/system';
-import Stack from '@mui/material/Stack';
-
-export default function UnstyledButtonsSimple() {
- return (
-
- Button
- Disabled
-
- );
-}
-
-const blue = {
- 200: '#99CCFF',
- 300: '#66B2FF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0066CC',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Button = styled(BaseButton)(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 600;
- font-size: 0.875rem;
- line-height: 1.5;
- background-color: ${blue[500]};
- padding: 8px 16px;
- border-radius: 8px;
- color: white;
- transition: all 150ms ease;
- cursor: pointer;
- border: 1px solid ${blue[500]};
- box-shadow: 0 2px 1px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)'
- }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]};
-
- &:hover {
- background-color: ${blue[600]};
- }
-
- &:active {
- background-color: ${blue[700]};
- box-shadow: none;
- transform: scale(0.99);
- }
-
- &:focus-visible {
- box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
- outline: none;
- }
-
- &.base--disabled {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
- border: 0;
- cursor: default;
- box-shadow: none;
- transform: scale(1);
- }
-`,
-);
diff --git a/docs/data/base/components/button/UnstyledButtonsSimple.tsx b/docs/data/base/components/button/UnstyledButtonsSimple.tsx
deleted file mode 100644
index 28c2022603..0000000000
--- a/docs/data/base/components/button/UnstyledButtonsSimple.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import * as React from 'react';
-import { Button as BaseButton } from '@base_ui/react/Button';
-import { styled } from '@mui/system';
-import Stack from '@mui/material/Stack';
-
-export default function UnstyledButtonsSimple() {
- return (
-
- Button
- Disabled
-
- );
-}
-
-const blue = {
- 200: '#99CCFF',
- 300: '#66B2FF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0066CC',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Button = styled(BaseButton)(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 600;
- font-size: 0.875rem;
- line-height: 1.5;
- background-color: ${blue[500]};
- padding: 8px 16px;
- border-radius: 8px;
- color: white;
- transition: all 150ms ease;
- cursor: pointer;
- border: 1px solid ${blue[500]};
- box-shadow: 0 2px 1px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)'
- }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]};
-
- &:hover {
- background-color: ${blue[600]};
- }
-
- &:active {
- background-color: ${blue[700]};
- box-shadow: none;
- transform: scale(0.99);
- }
-
- &:focus-visible {
- box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
- outline: none;
- }
-
- &.base--disabled {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
- border: 0;
- cursor: default;
- box-shadow: none;
- transform: scale(1);
- }
-`,
-);
diff --git a/docs/data/base/components/button/UnstyledButtonsSimple.tsx.preview b/docs/data/base/components/button/UnstyledButtonsSimple.tsx.preview
deleted file mode 100644
index b7b5add52a..0000000000
--- a/docs/data/base/components/button/UnstyledButtonsSimple.tsx.preview
+++ /dev/null
@@ -1,2 +0,0 @@
-Button
-Disabled
\ No newline at end of file
diff --git a/docs/data/base/components/button/UnstyledButtonsSpan.js b/docs/data/base/components/button/UnstyledButtonsSpan.js
deleted file mode 100644
index 5157fb7ce1..0000000000
--- a/docs/data/base/components/button/UnstyledButtonsSpan.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import * as React from 'react';
-import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button';
-import { styled } from '@mui/system';
-import Stack from '@mui/material/Stack';
-
-export default function UnstyledButtonsSpan() {
- return (
-
- Button
-
- Disabled
-
-
- );
-}
-
-const blue = {
- 200: '#99CCFF',
- 300: '#66B2FF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0066CC',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Button = styled(BaseButton)(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 600;
- font-size: 0.875rem;
- line-height: 1.5;
- background-color: ${blue[500]};
- padding: 8px 16px;
- border-radius: 8px;
- color: white;
- transition: all 150ms ease;
- cursor: pointer;
- border: 1px solid ${blue[500]};
- box-shadow: 0 2px 1px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)'
- }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]};
-
- &:hover {
- background-color: ${blue[600]};
- }
-
- &.${buttonClasses.active} {
- background-color: ${blue[700]};
- box-shadow: none;
- transform: scale(0.99);
- }
-
- &.${buttonClasses.focusVisible} {
- box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
- outline: none;
- }
-
- &.${buttonClasses.disabled} {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
- border: 0;
- cursor: default;
- box-shadow: none;
- transform: scale(1);
- }
-`,
-);
diff --git a/docs/data/base/components/button/UnstyledButtonsSpan.tsx b/docs/data/base/components/button/UnstyledButtonsSpan.tsx
deleted file mode 100644
index 5157fb7ce1..0000000000
--- a/docs/data/base/components/button/UnstyledButtonsSpan.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import * as React from 'react';
-import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button';
-import { styled } from '@mui/system';
-import Stack from '@mui/material/Stack';
-
-export default function UnstyledButtonsSpan() {
- return (
-
- Button
-
- Disabled
-
-
- );
-}
-
-const blue = {
- 200: '#99CCFF',
- 300: '#66B2FF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0066CC',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Button = styled(BaseButton)(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 600;
- font-size: 0.875rem;
- line-height: 1.5;
- background-color: ${blue[500]};
- padding: 8px 16px;
- border-radius: 8px;
- color: white;
- transition: all 150ms ease;
- cursor: pointer;
- border: 1px solid ${blue[500]};
- box-shadow: 0 2px 1px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)'
- }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]};
-
- &:hover {
- background-color: ${blue[600]};
- }
-
- &.${buttonClasses.active} {
- background-color: ${blue[700]};
- box-shadow: none;
- transform: scale(0.99);
- }
-
- &.${buttonClasses.focusVisible} {
- box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
- outline: none;
- }
-
- &.${buttonClasses.disabled} {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
- border: 0;
- cursor: default;
- box-shadow: none;
- transform: scale(1);
- }
-`,
-);
diff --git a/docs/data/base/components/button/UnstyledButtonsSpan.tsx.preview b/docs/data/base/components/button/UnstyledButtonsSpan.tsx.preview
deleted file mode 100644
index 3a8d9ed0ae..0000000000
--- a/docs/data/base/components/button/UnstyledButtonsSpan.tsx.preview
+++ /dev/null
@@ -1,4 +0,0 @@
-Button
-
- Disabled
-
\ No newline at end of file
diff --git a/docs/data/base/components/button/UnstyledLinkButton.js b/docs/data/base/components/button/UnstyledLinkButton.js
deleted file mode 100644
index 3064feff34..0000000000
--- a/docs/data/base/components/button/UnstyledLinkButton.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import * as React from 'react';
-import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button';
-import { prepareForSlot } from '@base_ui/react/utils';
-import { styled } from '@mui/system';
-import Stack from '@mui/material/Stack';
-import Link from 'next/link';
-
-const LinkSlot = prepareForSlot(Link);
-
-export default function UnstyledLinkButton() {
- return (
-
- Standard link
-
- Next.js link
-
-
- );
-}
-
-const blue = {
- 200: '#99CCFF',
- 300: '#66B2FF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0066CC',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Button = styled(BaseButton)(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 600;
- font-size: 0.875rem;
- line-height: 1.5;
- text-decoration: none;
- background-color: ${blue[500]};
- padding: 8px 16px;
- border-radius: 8px;
- color: white;
- transition: all 150ms ease;
- cursor: pointer;
- border: 1px solid ${blue[500]};
- box-shadow: 0 2px 1px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)'
- }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]};
-
- &:hover {
- background-color: ${blue[600]};
- }
-
- &.${buttonClasses.active} {
- background-color: ${blue[700]};
- box-shadow: none;
- transform: scale(0.99);
- }
-
- &.${buttonClasses.focusVisible} {
- box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
- outline: none;
- }
-
- &.${buttonClasses.disabled} {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
- border: 0;
- cursor: default;
- box-shadow: none;
- transform: scale(1);
- }
- `,
-);
diff --git a/docs/data/base/components/button/UnstyledLinkButton.tsx b/docs/data/base/components/button/UnstyledLinkButton.tsx
deleted file mode 100644
index 3064feff34..0000000000
--- a/docs/data/base/components/button/UnstyledLinkButton.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import * as React from 'react';
-import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button';
-import { prepareForSlot } from '@base_ui/react/utils';
-import { styled } from '@mui/system';
-import Stack from '@mui/material/Stack';
-import Link from 'next/link';
-
-const LinkSlot = prepareForSlot(Link);
-
-export default function UnstyledLinkButton() {
- return (
-
- Standard link
-
- Next.js link
-
-
- );
-}
-
-const blue = {
- 200: '#99CCFF',
- 300: '#66B2FF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0066CC',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const Button = styled(BaseButton)(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 600;
- font-size: 0.875rem;
- line-height: 1.5;
- text-decoration: none;
- background-color: ${blue[500]};
- padding: 8px 16px;
- border-radius: 8px;
- color: white;
- transition: all 150ms ease;
- cursor: pointer;
- border: 1px solid ${blue[500]};
- box-shadow: 0 2px 1px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)'
- }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]};
-
- &:hover {
- background-color: ${blue[600]};
- }
-
- &.${buttonClasses.active} {
- background-color: ${blue[700]};
- box-shadow: none;
- transform: scale(0.99);
- }
-
- &.${buttonClasses.focusVisible} {
- box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
- outline: none;
- }
-
- &.${buttonClasses.disabled} {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
- border: 0;
- cursor: default;
- box-shadow: none;
- transform: scale(1);
- }
- `,
-);
diff --git a/docs/data/base/components/button/UnstyledLinkButton.tsx.preview b/docs/data/base/components/button/UnstyledLinkButton.tsx.preview
deleted file mode 100644
index 3c5dd7dea1..0000000000
--- a/docs/data/base/components/button/UnstyledLinkButton.tsx.preview
+++ /dev/null
@@ -1,4 +0,0 @@
-Standard link
-
- Next.js link
-
\ No newline at end of file
diff --git a/docs/data/base/components/button/UseButton.js b/docs/data/base/components/button/UseButton.js
deleted file mode 100644
index af2a0f1cc5..0000000000
--- a/docs/data/base/components/button/UseButton.js
+++ /dev/null
@@ -1,110 +0,0 @@
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import clsx from 'clsx';
-import { styled } from '@mui/system';
-import Stack from '@mui/material/Stack';
-import { useButton } from '@base_ui/react/useButton';
-
-const CustomButton = React.forwardRef(function CustomButton(props, ref) {
- const { children, disabled } = props;
- const { active, focusVisible, getRootProps } = useButton({
- ...props,
- rootRef: ref,
- });
-
- return (
-
- {children}
-
- );
-});
-
-CustomButton.propTypes = {
- children: PropTypes.node,
- /**
- * If `true`, the component is disabled.
- * @default false
- */
- disabled: PropTypes.bool,
-};
-
-export default function UseButton() {
- return (
-
- console.log('click!')}>Button
- Disabled
-
- );
-}
-
-const blue = {
- 200: '#99CCFF',
- 300: '#66B2FF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0066CC',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const CustomButtonRoot = styled('button')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 600;
- font-size: 0.875rem;
- line-height: 1.5;
- background-color: ${blue[500]};
- padding: 8px 16px;
- border-radius: 8px;
- color: white;
- transition: all 150ms ease;
- cursor: pointer;
- border: 1px solid ${blue[500]};
- box-shadow: 0 2px 1px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)'
- }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]};
-
- &:hover {
- background-color: ${blue[600]};
- }
-
- &.active {
- background-color: ${blue[700]};
- box-shadow: none;
- transform: scale(0.99);
- }
-
- &.focusVisible {
- box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
- outline: none;
- }
-
- &.disabled {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
- border: 0;
- cursor: default;
- box-shadow: none;
- transform: scale(1);
- }
-`,
-);
diff --git a/docs/data/base/components/button/UseButton.tsx b/docs/data/base/components/button/UseButton.tsx
deleted file mode 100644
index 05fd30523e..0000000000
--- a/docs/data/base/components/button/UseButton.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import * as React from 'react';
-import clsx from 'clsx';
-import { styled } from '@mui/system';
-import Stack from '@mui/material/Stack';
-import { useButton } from '@base_ui/react/useButton';
-import { ButtonProps } from '@base_ui/react/Button';
-
-const CustomButton = React.forwardRef(function CustomButton(
- props: ButtonProps,
- ref: React.ForwardedRef,
-) {
- const { children, disabled } = props;
- const { active, focusVisible, getRootProps } = useButton({
- ...props,
- rootRef: ref,
- });
-
- return (
-
- {children}
-
- );
-});
-
-export default function UseButton() {
- return (
-
- console.log('click!')}>Button
- Disabled
-
- );
-}
-
-const blue = {
- 200: '#99CCFF',
- 300: '#66B2FF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0066CC',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const CustomButtonRoot = styled('button')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 600;
- font-size: 0.875rem;
- line-height: 1.5;
- background-color: ${blue[500]};
- padding: 8px 16px;
- border-radius: 8px;
- color: white;
- transition: all 150ms ease;
- cursor: pointer;
- border: 1px solid ${blue[500]};
- box-shadow: 0 2px 1px ${
- theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)'
- }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]};
-
- &:hover {
- background-color: ${blue[600]};
- }
-
- &.active {
- background-color: ${blue[700]};
- box-shadow: none;
- transform: scale(0.99);
- }
-
- &.focusVisible {
- box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]};
- outline: none;
- }
-
- &.disabled {
- background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
- border: 0;
- cursor: default;
- box-shadow: none;
- transform: scale(1);
- }
-`,
-);
diff --git a/docs/data/base/components/button/UseButton.tsx.preview b/docs/data/base/components/button/UseButton.tsx.preview
deleted file mode 100644
index 475a9b68ef..0000000000
--- a/docs/data/base/components/button/UseButton.tsx.preview
+++ /dev/null
@@ -1,2 +0,0 @@
- console.log('click!')}>Button
-Disabled
\ No newline at end of file
diff --git a/docs/data/base/components/button/button.md b/docs/data/base/components/button/button.md
deleted file mode 100644
index ad72ea436e..0000000000
--- a/docs/data/base/components/button/button.md
+++ /dev/null
@@ -1,154 +0,0 @@
----
-productId: base-ui
-title: React Button component and hook
-components: Button
-hooks: useButton
-githubLabel: 'component: button'
-waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/button/
----
-
-# Button
-
-Buttons let users take actions and make choices with a single tap.
-
-{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}
-
-{{"component": "modules/components/ComponentPageTabs.js"}}
-
-## Introduction
-
-The Button component replaces the native HTML `` element, and offers expanded options for styling and accessibility.
-
-{{"demo": "UnstyledButtonIntroduction", "defaultCodeOpen": false, "bg": "gradient"}}
-
-## Component
-
-```jsx
-import { Button } from '@base_ui/react/Button';
-```
-
-The Button behaves similar to the native HTML ``, so it wraps around the text that will be displayed on its surface.
-
-The following demo shows how to create and style two basic buttons.
-Notice that the second button cannot be clicked due to the `disabled` prop:
-
-{{"demo": "UnstyledButtonsSimple.js"}}
-
-### Anatomy
-
-The Button component is composed of a root `` slot with no interior slots:
-
-```html
-
-
-
-```
-
-### Custom structure
-
-Use the `slots.root` prop to override the root slot with a custom element:
-
-```jsx
-
-```
-
-:::info
-The `slots` prop is available on all non-utility Base components.
-See [Overriding component structure](/base-ui/guides/overriding-component-structure/) for full details.
-:::
-
-If you provide a non-interactive element such as a ``, the Button component will automatically add the necessary accessibility attributes.
-
-Compare the attributes on the `` in this demo with the Button from the previous demo—try inspecting them both with your browser's dev tools:
-
-{{"demo": "UnstyledButtonsSpan.js"}}
-
-:::warning
-If a Button is customized with a non-button element (for instance, ` `), it will not submit the form it's in when clicked.
-Similarly, `` will not reset its parent form.
-:::
-
-### Usage with TypeScript
-
-In TypeScript, you can specify the custom component type used in the `slots.root` as a generic parameter of the unstyled component. This way, you can safely provide the custom root's props directly on the component:
-
-```tsx
- slots={{ root: CustomComponent }} customProp />
-```
-
-The same applies for props specific to custom primitive elements:
-
-```tsx
- slots={{ root: 'img' }} src="button.png" />
-```
-
-## Hook
-
-```js
-import { useButton } from '@base_ui/react/useButton';
-```
-
-The `useButton` hook lets you apply the functionality of a Button to a fully custom component.
-It returns props to be placed on the custom component, along with fields representing the component's internal state.
-
-Hooks _do not_ support [slot props](#custom-structure), but they do support [customization props](#customization).
-
-:::info
-Hooks give you the most room for customization, but require more work to implement.
-With hooks, you can take full control over how your component is rendered, and define all the custom props and CSS classes you need.
-
-You may not need to use hooks unless you find that you're limited by the customization options of their component counterparts—for instance, if your component requires significantly different [structure](#anatomy).
-:::
-
-The following demo shows how to build the same buttons as those found in the [Component](#component) section above, but with the `useButton` hook:
-
-{{"demo": "UseButton.js", "defaultCodeOpen": true}}
-
-If you use a ref to store a reference to the button, pass it to the `useButton`'s `ref` parameter, as shown in the demo above.
-It will get merged with a ref used internally in the hook.
-
-:::warning
-Do not add the `ref` parameter to the button element manually, as the correct ref is already a part of the object returned by the `getRootProps` function.
-:::
-
-## Customization
-
-:::info
-The following features can be used with both components and hooks.
-For the sake of simplicity, demos, and code snippets primarily feature components.
-:::
-
-### Custom elements
-
-The Button accepts a wide range of custom elements beyond HTML elements.
-You can even use SVGs, as shown in the demo below:
-
-{{"demo": "UnstyledButtonCustom.js", "defaultCodeOpen": false}}
-
-### Using with links
-
-The following demo illustrates how to use the Button as a link, whether using the Base UI Button itself for the `href`, or with the [Next.js Link component](https://nextjs.org/docs/pages/api-reference/components/link):
-
-{{"demo": "UnstyledLinkButton.js", "defaultCodeOpen": true}}
-
-### Focus on disabled buttons
-
-Similarly to the native HTML `` element, the Button component can't receive focus when it's disabled.
-This may reduce its accessibility, as screen readers won't be able to announce the existence and state of the button.
-
-The `focusableWhenDisabled` prop lets you change this behavior.
-When this prop is set, the underlying Button does not set the `disabled` prop.
-Instead, `aria-disabled` is used, which makes the Button focusable.
-
-This should be used whenever the disabled Button needs to be read by screen readers.
-
-Base UI uses this prop internally in [menu items](/base-ui/react-menu/), making it possible to use the keyboard to navigate to disabled items (in compliance with [ARIA guidelines](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#x6-7-focusability-of-disabled-controls)).
-
-The following demo shows how the `focusableWhenDisabled` prop works—use the Tab key to navigate within this document to see that only the second Button accepts the focus:
-
-{{"demo": "UnstyledButtonsDisabledFocus.js"}}
-
-The `focusableWhenDisabled` prop works the same when the root slot is customized, except that the `aria-disabled` attribute is used no regardless of the prop's state.
-The ability to receive focus is controlled internally by the `tabindex` attribute.
-
-{{"demo": "UnstyledButtonsDisabledFocusCustom.js"}}
diff --git a/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/css/index.js b/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/css/index.js
new file mode 100644
index 0000000000..31331694ff
--- /dev/null
+++ b/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/css/index.js
@@ -0,0 +1,152 @@
+'use client';
+
+import * as React from 'react';
+import * as Checkbox from '@base_ui/react/Checkbox';
+import * as CheckboxGroup from '@base_ui/react/CheckboxGroup';
+import * as Field from '@base_ui/react/Field';
+import Check from '@mui/icons-material/Check';
+
+export default function UnstyledCheckboxIndeterminateGroup() {
+ return (
+
+
+
+ Colors
+
+
+
+
+
+
+
+ Red
+
+
+
+
+
+
+
+ Green
+
+
+
+
+
+
+
+ Blue
+
+
+
+
+
+
+ );
+}
+
+const grey = {
+ 100: '#E5EAF2',
+ 300: '#C7D0DD',
+ 500: '#9DA8B7',
+ 600: '#6B7A90',
+ 800: '#303740',
+ 900: '#1C2025',
+};
+
+function Styles() {
+ return (
+
+ );
+}
diff --git a/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/css/index.tsx b/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/css/index.tsx
new file mode 100644
index 0000000000..31331694ff
--- /dev/null
+++ b/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/css/index.tsx
@@ -0,0 +1,152 @@
+'use client';
+
+import * as React from 'react';
+import * as Checkbox from '@base_ui/react/Checkbox';
+import * as CheckboxGroup from '@base_ui/react/CheckboxGroup';
+import * as Field from '@base_ui/react/Field';
+import Check from '@mui/icons-material/Check';
+
+export default function UnstyledCheckboxIndeterminateGroup() {
+ return (
+
+
+
+ Colors
+
+
+
+
+
+
+
+ Red
+
+
+
+
+
+
+
+ Green
+
+
+
+
+
+
+
+ Blue
+
+
+
+
+
+
+ );
+}
+
+const grey = {
+ 100: '#E5EAF2',
+ 300: '#C7D0DD',
+ 500: '#9DA8B7',
+ 600: '#6B7A90',
+ 800: '#303740',
+ 900: '#1C2025',
+};
+
+function Styles() {
+ return (
+
+ );
+}
diff --git a/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/system/index.js b/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/system/index.js
new file mode 100644
index 0000000000..26b73c5315
--- /dev/null
+++ b/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/system/index.js
@@ -0,0 +1,125 @@
+'use client';
+
+import * as React from 'react';
+import { css, styled } from '@mui/system';
+import * as BaseCheckbox from '@base_ui/react/Checkbox';
+import * as CheckboxGroup from '@base_ui/react/CheckboxGroup';
+import * as Field from '@base_ui/react/Field';
+import Check from '@mui/icons-material/Check';
+
+export default function UnstyledCheckboxIndeterminateGroup() {
+ return (
+
+
+
+ Colors
+
+
+
+
+
+
+
+ Red
+
+
+
+
+
+
+
+ Green
+
+
+
+
+
+
+
+ Blue
+
+
+
+
+
+ );
+}
+
+const blue = {
+ 400: '#3399FF',
+ 600: '#0072E6',
+ 800: '#004C99',
+};
+
+const grey = {
+ 100: '#E5EAF2',
+ 400: '#B0B8C4',
+ 800: '#303740',
+};
+
+const labelStyles = css`
+ display: flex;
+ gap: 8px;
+ margin-bottom: 8px;
+`;
+
+const FieldRoot = styled(Field.Root)`
+ display: flex;
+`;
+
+const CheckboxLabel = styled(Field.Label)`
+ ${labelStyles}
+ padding-left: 8px;
+`;
+
+const CheckboxGroupLabel = styled(Field.Label)`
+ font-size: 17px;
+ font-weight: bold;
+ ${labelStyles}
+`;
+
+const Checkbox = styled(BaseCheckbox.Root)(
+ ({ theme }) => `
+ width: 24px;
+ height: 24px;
+ padding: 0;
+ border-radius: 4px;
+ border: 2px solid ${blue[600]};
+ background: none;
+ transition-property: background, border-color;
+ transition-duration: 0.15s;
+ outline: none;
+
+ &[data-disabled] {
+ opacity: 0.4;
+ cursor: not-allowed;
+ }
+
+ &:focus-visible {
+ outline: 2px solid ${theme.palette.mode === 'dark' ? blue[800] : blue[400]};
+ outline-offset: 2px;
+ }
+
+ &[data-state="checked"], &[data-state="mixed"] {
+ border-color: transparent;
+ background: ${blue[600]};
+ }
+ `,
+);
+
+const CheckIcon = styled(Check)`
+ height: 100%;
+ width: 100%;
+`;
+
+const Indicator = styled(BaseCheckbox.Indicator)`
+ height: 100%;
+ display: inline-block;
+ visibility: hidden;
+ color: ${grey[100]};
+
+ &[data-state='checked'],
+ &[data-state='mixed'] {
+ visibility: visible;
+ }
+`;
diff --git a/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/system/index.tsx b/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/system/index.tsx
new file mode 100644
index 0000000000..26b73c5315
--- /dev/null
+++ b/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/system/index.tsx
@@ -0,0 +1,125 @@
+'use client';
+
+import * as React from 'react';
+import { css, styled } from '@mui/system';
+import * as BaseCheckbox from '@base_ui/react/Checkbox';
+import * as CheckboxGroup from '@base_ui/react/CheckboxGroup';
+import * as Field from '@base_ui/react/Field';
+import Check from '@mui/icons-material/Check';
+
+export default function UnstyledCheckboxIndeterminateGroup() {
+ return (
+
+
+
+ Colors
+
+
+
+
+
+
+
+ Red
+
+
+
+
+
+
+
+ Green
+
+
+
+
+
+
+
+ Blue
+
+
+
+
+
+ );
+}
+
+const blue = {
+ 400: '#3399FF',
+ 600: '#0072E6',
+ 800: '#004C99',
+};
+
+const grey = {
+ 100: '#E5EAF2',
+ 400: '#B0B8C4',
+ 800: '#303740',
+};
+
+const labelStyles = css`
+ display: flex;
+ gap: 8px;
+ margin-bottom: 8px;
+`;
+
+const FieldRoot = styled(Field.Root)`
+ display: flex;
+`;
+
+const CheckboxLabel = styled(Field.Label)`
+ ${labelStyles}
+ padding-left: 8px;
+`;
+
+const CheckboxGroupLabel = styled(Field.Label)`
+ font-size: 17px;
+ font-weight: bold;
+ ${labelStyles}
+`;
+
+const Checkbox = styled(BaseCheckbox.Root)(
+ ({ theme }) => `
+ width: 24px;
+ height: 24px;
+ padding: 0;
+ border-radius: 4px;
+ border: 2px solid ${blue[600]};
+ background: none;
+ transition-property: background, border-color;
+ transition-duration: 0.15s;
+ outline: none;
+
+ &[data-disabled] {
+ opacity: 0.4;
+ cursor: not-allowed;
+ }
+
+ &:focus-visible {
+ outline: 2px solid ${theme.palette.mode === 'dark' ? blue[800] : blue[400]};
+ outline-offset: 2px;
+ }
+
+ &[data-state="checked"], &[data-state="mixed"] {
+ border-color: transparent;
+ background: ${blue[600]};
+ }
+ `,
+);
+
+const CheckIcon = styled(Check)`
+ height: 100%;
+ width: 100%;
+`;
+
+const Indicator = styled(BaseCheckbox.Indicator)`
+ height: 100%;
+ display: inline-block;
+ visibility: hidden;
+ color: ${grey[100]};
+
+ &[data-state='checked'],
+ &[data-state='mixed'] {
+ visibility: visible;
+ }
+`;
diff --git a/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/tailwind/index.js b/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/tailwind/index.js
new file mode 100644
index 0000000000..41a8de1373
--- /dev/null
+++ b/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/tailwind/index.js
@@ -0,0 +1,126 @@
+'use client';
+
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import { useTheme } from '@mui/system';
+import * as BaseCheckbox from '@base_ui/react/Checkbox';
+import * as CheckboxGroup from '@base_ui/react/CheckboxGroup';
+import * as Field from '@base_ui/react/Field';
+import Check from '@mui/icons-material/Check';
+
+function classNames(...classes) {
+ return classes.filter(Boolean).join(' ');
+}
+
+function useIsDarkMode() {
+ const theme = useTheme();
+ return theme.palette.mode === 'dark';
+}
+
+function Label(props) {
+ return (
+ // eslint-disable-next-line jsx-a11y/label-has-associated-control, jsx-a11y/no-noninteractive-element-interactions
+ e.preventDefault()}
+ {...props}
+ />
+ );
+}
+
+export default function UnstyledCheckboxIntroduction() {
+ // Replace this with your app logic for determining dark mode
+ const isDarkMode = useIsDarkMode();
+
+ return (
+
+
+
+ Colors
+
+
+
+
+
+
+
+ Red
+
+
+
+
+
+
+
+ Green
+
+
+
+
+
+
+
+ Blue
+
+
+
+
+
+ );
+}
+
+const Checkbox = React.forwardRef(function Checkbox(props, ref) {
+ return (
+
+ classNames(
+ 'w-6 h-6 p-0 rounded-md',
+ 'border-2 border-solid border-purple-500',
+ 'outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-purple-500 focus-visible:ring-opacity-60',
+ 'transition-colors duration-150',
+ state.disabled && 'opacity-40 cursor-not-allowed',
+ state.checked && 'bg-purple-500',
+ !state.checked && 'bg-transparent',
+ typeof props.className === 'function'
+ ? props.className(state)
+ : props.className,
+ )
+ }
+ />
+ );
+});
+
+Checkbox.propTypes = {
+ /**
+ * Class names applied to the element or a function that returns them based on the component's state.
+ */
+ className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
+};
+
+const Indicator = React.forwardRef(function Indicator(props, ref) {
+ return (
+
+ classNames(
+ 'h-full inline-block invisible data-[state=checked]:visible text-gray-100',
+ typeof props.className === 'function'
+ ? props.className(state)
+ : props.className,
+ )
+ }
+ >
+
+
+ );
+});
+
+Indicator.propTypes = {
+ /**
+ * Class names applied to the element or a function that returns them based on the component's state.
+ */
+ className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
+};
diff --git a/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/tailwind/index.tsx b/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/tailwind/index.tsx
new file mode 100644
index 0000000000..e6f2e72b61
--- /dev/null
+++ b/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupIntroduction/tailwind/index.tsx
@@ -0,0 +1,115 @@
+'use client';
+
+import * as React from 'react';
+import { useTheme } from '@mui/system';
+import * as BaseCheckbox from '@base_ui/react/Checkbox';
+import * as CheckboxGroup from '@base_ui/react/CheckboxGroup';
+import * as Field from '@base_ui/react/Field';
+import Check from '@mui/icons-material/Check';
+
+function classNames(...classes: Array) {
+ return classes.filter(Boolean).join(' ');
+}
+
+function useIsDarkMode() {
+ const theme = useTheme();
+ return theme.palette.mode === 'dark';
+}
+
+function Label(props: React.ComponentPropsWithoutRef<'label'>) {
+ return (
+ // eslint-disable-next-line jsx-a11y/label-has-associated-control, jsx-a11y/no-noninteractive-element-interactions
+ e.preventDefault()}
+ {...props}
+ />
+ );
+}
+
+export default function UnstyledCheckboxIntroduction() {
+ // Replace this with your app logic for determining dark mode
+ const isDarkMode = useIsDarkMode();
+
+ return (
+
+
+
+ Colors
+
+
+
+
+
+
+
+ Red
+
+
+
+
+
+
+
+ Green
+
+
+
+
+
+
+
+ Blue
+
+
+
+
+
+ );
+}
+
+const Checkbox = React.forwardRef(
+ function Checkbox(props, ref) {
+ return (
+
+ classNames(
+ 'w-6 h-6 p-0 rounded-md',
+ 'border-2 border-solid border-purple-500',
+ 'outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-purple-500 focus-visible:ring-opacity-60',
+ 'transition-colors duration-150',
+ state.disabled && 'opacity-40 cursor-not-allowed',
+ state.checked && 'bg-purple-500',
+ !state.checked && 'bg-transparent',
+ typeof props.className === 'function'
+ ? props.className(state)
+ : props.className,
+ )
+ }
+ />
+ );
+ },
+);
+
+const Indicator = React.forwardRef(
+ function Indicator(props, ref) {
+ return (
+
+ classNames(
+ 'h-full inline-block invisible data-[state=checked]:visible text-gray-100',
+ typeof props.className === 'function'
+ ? props.className(state)
+ : props.className,
+ )
+ }
+ >
+
+
+ );
+ },
+);
diff --git a/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupNested.js b/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupNested.js
new file mode 100644
index 0000000000..d7ce96b207
--- /dev/null
+++ b/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupNested.js
@@ -0,0 +1,152 @@
+'use client';
+
+import * as React from 'react';
+import * as BaseCheckbox from '@base_ui/react/Checkbox';
+import * as CheckboxGroup from '@base_ui/react/CheckboxGroup';
+import * as Field from '@base_ui/react/Field';
+import { styled } from '@mui/system';
+import HorizontalRule from '@mui/icons-material/HorizontalRule';
+import Check from '@mui/icons-material/Check';
+
+const colors = ['red', 'green', 'blue'];
+
+export default function UnstyledCheckboxGroupNested() {
+ const [value, setValue] = React.useState([]);
+
+ return (
+
+
+ Colors
+ }>
+
+ (
+
+ {indeterminate ? : }
+
+ )}
+ />
+
+ All Colors
+
+
+ {colors.map((color) => (
+ }>
+
+
+
+
+
+ {color}
+
+ ))}
+
+
+
+ );
+}
+
+const blue = {
+ 400: '#3399FF',
+ 600: '#0072E6',
+ 800: '#004C99',
+};
+
+const grey = {
+ 100: '#E5EAF2',
+ 400: '#B0B8C4',
+ 800: '#303740',
+};
+
+const CheckboxGroupLabel = styled(Field.Label)`
+ display: block;
+ font-weight: bold;
+ font-size: 18px;
+ margin-bottom: 8px;
+`;
+
+const Checkbox = styled(BaseCheckbox.Root)(
+ ({ theme }) => `
+ width: 24px;
+ height: 24px;
+ padding: 0;
+ border-radius: 4px;
+ border: 2px solid ${blue[600]};
+ background: none;
+ transition-property: background, border-color;
+ transition-duration: 0.15s;
+ outline: none;
+
+ &[data-disabled] {
+ opacity: 0.4;
+ cursor: not-allowed;
+ }
+
+ &:focus-visible {
+ outline: 2px solid ${theme.palette.mode === 'dark' ? blue[800] : blue[400]};
+ outline-offset: 2px;
+ }
+
+ &[data-state="checked"], &[data-state="mixed"] {
+ border-color: transparent;
+ background: ${blue[600]};
+ }
+ `,
+);
+
+const HorizontalRuleIcon = styled(HorizontalRule)`
+ height: 100%;
+ width: 100%;
+`;
+
+const CheckIcon = styled(Check)`
+ height: 100%;
+ width: 100%;
+`;
+
+const Indicator = styled(BaseCheckbox.Indicator)`
+ height: 100%;
+ display: inline-block;
+ visibility: hidden;
+ color: ${grey[100]};
+
+ &[data-state='checked'],
+ &[data-state='mixed'] {
+ visibility: visible;
+ }
+`;
+
+const FieldRoot = styled(Field.Root)`
+ display: flex;
+ align-items: center;
+ margin-bottom: 8px;
+ padding: 0;
+`;
+
+const List = styled('ul')`
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ margin-left: 32px;
+`;
+
+const FieldListItem = styled(Field.Root)`
+ display: flex;
+ align-items: center;
+
+ &:not(:last-child) {
+ margin-bottom: 8px;
+ }
+`;
+
+const CheckboxLabel = styled(Field.Label)`
+ display: flex;
+ gap: 8px;
+ text-transform: capitalize;
+ padding-left: 8px;
+`;
diff --git a/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupNested.tsx b/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupNested.tsx
new file mode 100644
index 0000000000..a19cf961c3
--- /dev/null
+++ b/docs/data/base/components/checkbox-group/UnstyledCheckboxGroupNested.tsx
@@ -0,0 +1,152 @@
+'use client';
+
+import * as React from 'react';
+import * as BaseCheckbox from '@base_ui/react/Checkbox';
+import * as CheckboxGroup from '@base_ui/react/CheckboxGroup';
+import * as Field from '@base_ui/react/Field';
+import { styled } from '@mui/system';
+import HorizontalRule from '@mui/icons-material/HorizontalRule';
+import Check from '@mui/icons-material/Check';
+
+const colors = ['red', 'green', 'blue'];
+
+export default function UnstyledCheckboxGroupNested() {
+ const [value, setValue] = React.useState([]);
+
+ return (
+
+
+ Colors
+ }>
+
+ (
+
+ {indeterminate ? : }
+
+ )}
+ />
+
+ All Colors
+
+
+ {colors.map((color) => (
+ }>
+
+
+
+
+
+ {color}
+
+ ))}
+
+
+
+ );
+}
+
+const blue = {
+ 400: '#3399FF',
+ 600: '#0072E6',
+ 800: '#004C99',
+};
+
+const grey = {
+ 100: '#E5EAF2',
+ 400: '#B0B8C4',
+ 800: '#303740',
+};
+
+const CheckboxGroupLabel = styled(Field.Label)`
+ display: block;
+ font-weight: bold;
+ font-size: 18px;
+ margin-bottom: 8px;
+`;
+
+const Checkbox = styled(BaseCheckbox.Root)(
+ ({ theme }) => `
+ width: 24px;
+ height: 24px;
+ padding: 0;
+ border-radius: 4px;
+ border: 2px solid ${blue[600]};
+ background: none;
+ transition-property: background, border-color;
+ transition-duration: 0.15s;
+ outline: none;
+
+ &[data-disabled] {
+ opacity: 0.4;
+ cursor: not-allowed;
+ }
+
+ &:focus-visible {
+ outline: 2px solid ${theme.palette.mode === 'dark' ? blue[800] : blue[400]};
+ outline-offset: 2px;
+ }
+
+ &[data-state="checked"], &[data-state="mixed"] {
+ border-color: transparent;
+ background: ${blue[600]};
+ }
+ `,
+);
+
+const HorizontalRuleIcon = styled(HorizontalRule)`
+ height: 100%;
+ width: 100%;
+`;
+
+const CheckIcon = styled(Check)`
+ height: 100%;
+ width: 100%;
+`;
+
+const Indicator = styled(BaseCheckbox.Indicator)`
+ height: 100%;
+ display: inline-block;
+ visibility: hidden;
+ color: ${grey[100]};
+
+ &[data-state='checked'],
+ &[data-state='mixed'] {
+ visibility: visible;
+ }
+`;
+
+const FieldRoot = styled(Field.Root)`
+ display: flex;
+ align-items: center;
+ margin-bottom: 8px;
+ padding: 0;
+`;
+
+const List = styled('ul')`
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ margin-left: 32px;
+`;
+
+const FieldListItem = styled(Field.Root)`
+ display: flex;
+ align-items: center;
+
+ &:not(:last-child) {
+ margin-bottom: 8px;
+ }
+`;
+
+const CheckboxLabel = styled(Field.Label)`
+ display: flex;
+ gap: 8px;
+ text-transform: capitalize;
+ padding-left: 8px;
+`;
diff --git a/docs/data/base/components/checkbox-group/checkbox-group.md b/docs/data/base/components/checkbox-group/checkbox-group.md
new file mode 100644
index 0000000000..d20c5af0f1
--- /dev/null
+++ b/docs/data/base/components/checkbox-group/checkbox-group.md
@@ -0,0 +1,136 @@
+---
+productId: base-ui
+title: React Checkbox Group component and hook
+components: CheckboxGroupRoot
+githubLabel: 'component: checkbox'
+waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/
+packageName: '@base_ui/react'
+---
+
+# Checkbox Group
+
+Checkbox Groups combine a series of checkboxes together.
+
+{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}
+
+{{"component": "modules/components/ComponentPageTabs.js"}}
+
+{{"demo": "UnstyledCheckboxGroupIntroduction", "defaultCodeOpen": false, "bg": "gradient"}}
+
+## Installation
+
+Base UI components are all available as a single package.
+
+
+
+```bash npm
+npm install @base_ui/react
+```
+
+```bash yarn
+yarn add @base_ui/react
+```
+
+```bash pnpm
+pnpm add @base_ui/react
+```
+
+
+
+Once you have the package installed, import the components:
+
+```ts
+import * as CheckboxGroup from '@base_ui/react/CheckboxGroup';
+import * as Checkbox from '@base_ui/react/Checkbox';
+```
+
+## Anatomy
+
+Checkbox Group is composed of a `Root` component and `Checkbox` components:
+
+- ` ` renders a `` with a `group` role.
+- `
` renders an individual `
` checkbox.
+
+```tsx
+
+
+
+
+```
+
+## Labeling
+
+`Field` components are used to label the Checkbox Group and individual Checkboxes:
+
+```jsx
+import * as Field from '@base_ui/react/Field';
+```
+
+```tsx
+
+
+ Colors
+
+
+ Red
+
+
+
+ Blue
+
+
+
+```
+
+## Controlled
+
+The `value` and `onValueChange` props control the Checkbox Group with external state. `value` is an array of strings matching the `name` props of Checkboxes that are currently checked:
+
+```jsx
+const [value, setValue] = useState(['red']);
+
+return (
+
+ {/* Checked */}
+
+
+
+);
+```
+
+## Parent Checkbox
+
+A Checkbox can control a group of child Checkboxes.
+
+1. Make `CheckboxGroup.Root` controlled and add `allValues` as a prop — an array of strings that contains the names of all the child checkboxes.
+2. Add a `parent` prop to the `Checkbox.Root` component that controls the other (child) Checkboxes inside the group.
+3. Give the child Checkboxes a `name` prop that identifies them inside the `allValues` array.
+
+```jsx
+const allValues = ['a', 'b', 'c'];
+
+function App() {
+ const [value, setValue] = useState([]);
+ return (
+
+
+ {allValues.map((value) => (
+
+ ))}
+
+ );
+}
+```
+
+{{"demo": "UnstyledCheckboxGroupNested.js"}}
+
+To preserve the initial state of the child checkboxes when the parent checkbox is toggled, set the `preserveChildStates` prop to `true`:
+
+```tsx
+
+
+ {allValues.map((value) => (
+
+ ))}
+
+```
diff --git a/docs/data/base/components/checkbox/UnstyledCheckboxIndeterminateGroup.js b/docs/data/base/components/checkbox/UnstyledCheckboxIndeterminateGroup.js
index af25dd54fe..2cd48c2a8c 100644
--- a/docs/data/base/components/checkbox/UnstyledCheckboxIndeterminateGroup.js
+++ b/docs/data/base/components/checkbox/UnstyledCheckboxIndeterminateGroup.js
@@ -7,10 +7,11 @@ import Check from '@mui/icons-material/Check';
const colors = ['Red', 'Green', 'Blue'];
export default function UnstyledCheckboxIndeterminateGroup() {
- const [checkedValues, setCheckedValues] = React.useState([false, true, false]);
+ const [checkedValues, setCheckedValues] = React.useState(['Green']);
- const isChecked = checkedValues.every((value) => value);
- const isIndeterminate = checkedValues.some((value) => value) && !isChecked;
+ const isChecked = checkedValues.length === colors.length;
+ const isIndeterminate =
+ checkedValues.length !== colors.length && checkedValues.length > 0;
const id = React.useId();
@@ -22,28 +23,31 @@ export default function UnstyledCheckboxIndeterminateGroup() {
aria-controls={colors.map((color) => `${id}-${color}`).join(' ')}
indeterminate={isIndeterminate}
checked={isChecked}
- onChange={(event) => {
- const checked = event.target.checked;
- setCheckedValues([checked, checked, checked]);
+ onCheckedChange={(checked) => {
+ setCheckedValues(checked ? colors : []);
}}
>
{isIndeterminate ? : }
- e.preventDefault()}>
+ event.preventDefault()}>
Colors
- {colors.map((color, index) => (
+ {colors.map((color) => (
{
+ checked={checkedValues.includes(color)}
+ onCheckedChange={(checked) => {
const newCheckedValues = [...checkedValues];
- newCheckedValues[index] = event.target.checked;
+ if (checked) {
+ newCheckedValues.push(color);
+ } else {
+ newCheckedValues.splice(newCheckedValues.indexOf(color), 1);
+ }
setCheckedValues(newCheckedValues);
}}
>
@@ -53,7 +57,7 @@ export default function UnstyledCheckboxIndeterminateGroup() {
e.preventDefault()}
+ onMouseDown={(event) => event.preventDefault()}
>
{color}
diff --git a/docs/data/base/components/checkbox/UnstyledCheckboxIndeterminateGroup.tsx b/docs/data/base/components/checkbox/UnstyledCheckboxIndeterminateGroup.tsx
index af25dd54fe..2cd48c2a8c 100644
--- a/docs/data/base/components/checkbox/UnstyledCheckboxIndeterminateGroup.tsx
+++ b/docs/data/base/components/checkbox/UnstyledCheckboxIndeterminateGroup.tsx
@@ -7,10 +7,11 @@ import Check from '@mui/icons-material/Check';
const colors = ['Red', 'Green', 'Blue'];
export default function UnstyledCheckboxIndeterminateGroup() {
- const [checkedValues, setCheckedValues] = React.useState([false, true, false]);
+ const [checkedValues, setCheckedValues] = React.useState(['Green']);
- const isChecked = checkedValues.every((value) => value);
- const isIndeterminate = checkedValues.some((value) => value) && !isChecked;
+ const isChecked = checkedValues.length === colors.length;
+ const isIndeterminate =
+ checkedValues.length !== colors.length && checkedValues.length > 0;
const id = React.useId();
@@ -22,28 +23,31 @@ export default function UnstyledCheckboxIndeterminateGroup() {
aria-controls={colors.map((color) => `${id}-${color}`).join(' ')}
indeterminate={isIndeterminate}
checked={isChecked}
- onChange={(event) => {
- const checked = event.target.checked;
- setCheckedValues([checked, checked, checked]);
+ onCheckedChange={(checked) => {
+ setCheckedValues(checked ? colors : []);
}}
>
{isIndeterminate ? : }
- e.preventDefault()}>
+ event.preventDefault()}>
Colors
- {colors.map((color, index) => (
+ {colors.map((color) => (
{
+ checked={checkedValues.includes(color)}
+ onCheckedChange={(checked) => {
const newCheckedValues = [...checkedValues];
- newCheckedValues[index] = event.target.checked;
+ if (checked) {
+ newCheckedValues.push(color);
+ } else {
+ newCheckedValues.splice(newCheckedValues.indexOf(color), 1);
+ }
setCheckedValues(newCheckedValues);
}}
>
@@ -53,7 +57,7 @@ export default function UnstyledCheckboxIndeterminateGroup() {
e.preventDefault()}
+ onMouseDown={(event) => event.preventDefault()}
>
{color}
diff --git a/docs/data/base/components/checkbox/checkbox.md b/docs/data/base/components/checkbox/checkbox.md
index a3cb7f58f7..859573f5a0 100644
--- a/docs/data/base/components/checkbox/checkbox.md
+++ b/docs/data/base/components/checkbox/checkbox.md
@@ -10,108 +10,78 @@ packageName: '@base_ui/react'
# Checkbox
-Checkboxes give users binary choices when presented with multiple options in a series.
+Checkbox gives users a binary choice between multiple options in a series.
{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}
{{"component": "modules/components/ComponentPageTabs.js"}}
-## Introduction
-
-The Checkbox component provides users with a checkbox for toggling a checked state.
-
{{"demo": "UnstyledCheckboxIntroduction", "defaultCodeOpen": false, "bg": "gradient"}}
-## Component
+## Installation
-```jsx
-import * as Checkbox from '@base_ui/react/Checkbox';
-```
+Base UI components are all available as a single package.
-### Anatomy
+
-The `Checkbox` component is composed of a root component and an indicator child component:
-
-```tsx
-
-
-
+```bash npm
+npm install @base_ui/react
```
-The indicator can contain children, such as an icon:
-
-```tsx
-
-
-
-
-
+```bash yarn
+yarn add @base_ui/react
```
-The indicator conditionally unmounts its children when the checkbox is unchecked. For CSS animations, you can use the `keepMounted` prop to transition `visibility` and `opacity` for example:
-
-```tsx
-
-
-
-
-
+```bash pnpm
+pnpm add @base_ui/react
```
-### Custom structure
+
-Use the `render` prop to override the rendered checkbox or indicator element with your own components:
+Once you have the package installed, import the component.
-```jsx
- }>
- } />
-
+```ts
+import * as Checkbox from '@base_ui/react/Checkbox';
```
-To ensure behavior works as expected:
+## Anatomy
-- **Forward all props**: Your component should spread all props to the underlying element.
-- **Forward the `ref`**: Your component should use [`forwardRef`](https://react.dev/reference/react/forwardRef) to ensure the Checkbox components can access the element via a ref.
+Checkbox is composed of two components:
-A custom component that adheres to these two principles looks like this:
+- ` ` renders a ``.
+- ` ` renders a `` for providing a visual indicator. You could place an icon inside this component.
-```jsx
-const MyCheckbox = React.forwardRef(function MyCheckbox(props, ref) {
- return ;
-});
+```tsx
+
+
+
```
-### Indeterminate state
+## Indeterminate state
-To make the checkbox indeterminate, add the `indeterminate` prop to override the appearance of the checkbox. The checkbox remains in an indeterminate state regardless of user interaction until set back to `false`.
+To make the Checkbox indeterminate, add the `indeterminate` prop to override the appearance of the Checkbox. The Checkbox remains in an indeterminate state regardless of user interaction until set back to `false`.
{{"demo": "UnstyledCheckboxIndeterminate.js"}}
-An indeterminate checkbox's main use case is representing the state of a parent checkbox where only some of its children are checked:
+The primary use case for an indeterminate checkbox is representing the state of a parent checkbox where only some of its children are checked.
{{"demo": "UnstyledCheckboxIndeterminateGroup.js", "defaultCodeOpen": false}}
-It's a **visual-only** state, so it can still have its internal `checked` state change.
-
-## Hook
+It's a _visual-only_ state, so its internal `checked` state can still be changed.
-```js
-import { useCheckbox } from '@base_ui/react/useCheckbox';
-```
+## Overriding default components
-The `useCheckbox` hook lets you apply the functionality of a Checkbox to a fully custom component.
-It returns props to be placed on the custom component, along with fields representing the component's internal state.
+Use the `render` prop to override the rendered checkbox or indicator element with your own components.
-:::info
-Hooks give you the most room for customization, but require more work to implement.
-With hooks, you can take full control over how your component is rendered, and define all the custom props and CSS classes you need.
-
-You may not need to use hooks unless you find that you're limited by the customization options of their component counterparts—for instance, if your component requires significantly different [HTML structure](#anatomy).
-:::
+```jsx
+ }>
+ } />
+
+```
## Accessibility
-Ensure the checkbox has an accessible name via a `label` element.
+Ensure the Checkbox has an accessible name via a `` element.
```jsx
diff --git a/docs/data/base/components/click-away-listener/ClickAway.js b/docs/data/base/components/click-away-listener/ClickAway.js
index b8fd5355c3..37c8d3aef2 100644
--- a/docs/data/base/components/click-away-listener/ClickAway.js
+++ b/docs/data/base/components/click-away-listener/ClickAway.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import Box from '@mui/material/Box';
-import { ClickAwayListener } from '@base_ui/react/ClickAwayListener';
+import { ClickAwayListener } from '@base_ui/react/legacy/ClickAwayListener';
export default function ClickAway() {
const [open, setOpen] = React.useState(false);
diff --git a/docs/data/base/components/click-away-listener/ClickAway.tsx b/docs/data/base/components/click-away-listener/ClickAway.tsx
index 1c6119016a..f2ba90bec2 100644
--- a/docs/data/base/components/click-away-listener/ClickAway.tsx
+++ b/docs/data/base/components/click-away-listener/ClickAway.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import Box from '@mui/material/Box';
-import { ClickAwayListener } from '@base_ui/react/ClickAwayListener';
+import { ClickAwayListener } from '@base_ui/react/legacy/ClickAwayListener';
import { SxProps } from '@mui/system';
export default function ClickAway() {
diff --git a/docs/data/base/components/click-away-listener/LeadingClickAway.js b/docs/data/base/components/click-away-listener/LeadingClickAway.js
index 153a79f7e6..1420125bcf 100644
--- a/docs/data/base/components/click-away-listener/LeadingClickAway.js
+++ b/docs/data/base/components/click-away-listener/LeadingClickAway.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import Box from '@mui/material/Box';
-import { ClickAwayListener } from '@base_ui/react/ClickAwayListener';
+import { ClickAwayListener } from '@base_ui/react/legacy/ClickAwayListener';
export default function LeadingClickAway() {
const [open, setOpen] = React.useState(false);
diff --git a/docs/data/base/components/click-away-listener/LeadingClickAway.tsx b/docs/data/base/components/click-away-listener/LeadingClickAway.tsx
index c7169d9699..83a3ee96a8 100644
--- a/docs/data/base/components/click-away-listener/LeadingClickAway.tsx
+++ b/docs/data/base/components/click-away-listener/LeadingClickAway.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import Box from '@mui/material/Box';
-import { ClickAwayListener } from '@base_ui/react/ClickAwayListener';
+import { ClickAwayListener } from '@base_ui/react/legacy/ClickAwayListener';
import { SxProps } from '@mui/system';
export default function LeadingClickAway() {
diff --git a/docs/data/base/components/click-away-listener/PortalClickAway.js b/docs/data/base/components/click-away-listener/PortalClickAway.js
index df8191865d..e6277147fc 100644
--- a/docs/data/base/components/click-away-listener/PortalClickAway.js
+++ b/docs/data/base/components/click-away-listener/PortalClickAway.js
@@ -1,7 +1,7 @@
import * as React from 'react';
import Box from '@mui/material/Box';
-import { ClickAwayListener } from '@base_ui/react/ClickAwayListener';
-import { Portal } from '@base_ui/react/Portal';
+import { ClickAwayListener } from '@base_ui/react/legacy/ClickAwayListener';
+import { Portal } from '@base_ui/react/legacy/Portal';
export default function PortalClickAway() {
const [open, setOpen] = React.useState(false);
diff --git a/docs/data/base/components/click-away-listener/PortalClickAway.tsx b/docs/data/base/components/click-away-listener/PortalClickAway.tsx
index e0999bef00..109094ec46 100644
--- a/docs/data/base/components/click-away-listener/PortalClickAway.tsx
+++ b/docs/data/base/components/click-away-listener/PortalClickAway.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import Box from '@mui/material/Box';
-import { ClickAwayListener } from '@base_ui/react/ClickAwayListener';
-import { Portal } from '@base_ui/react/Portal';
+import { ClickAwayListener } from '@base_ui/react/legacy/ClickAwayListener';
+import { Portal } from '@base_ui/react/legacy/Portal';
import { SxProps } from '@mui/system';
export default function PortalClickAway() {
diff --git a/docs/data/base/components/click-away-listener/click-away-listener.md b/docs/data/base/components/click-away-listener/click-away-listener.md
index 9020a85040..32aeb2867d 100644
--- a/docs/data/base/components/click-away-listener/click-away-listener.md
+++ b/docs/data/base/components/click-away-listener/click-away-listener.md
@@ -25,7 +25,7 @@ Click-Away Listener also supports the [Portal](/base-ui/react-portal/) component
## Component
```jsx
-import { ClickAwayListener } from '@base_ui/react/ClickAwayListener';
+import { ClickAwayListener } from '@base_ui/react/legacy/ClickAwayListener';
```
The demo below shows how to hide a menu dropdown when users click anywhere else on the page:
diff --git a/docs/data/base/components/collapsible/CssAnimatedCollapsible.js b/docs/data/base/components/collapsible/CssAnimatedCollapsible.js
new file mode 100644
index 0000000000..fe5e8fd2d9
--- /dev/null
+++ b/docs/data/base/components/collapsible/CssAnimatedCollapsible.js
@@ -0,0 +1,139 @@
+'use client';
+import * as React from 'react';
+import { useTheme } from '@mui/system';
+import * as Collapsible from '@base_ui/react/Collapsible';
+
+export default function CssAnimatedCollapsible() {
+ const [open, setOpen] = React.useState(true);
+ return (
+
+
+
+
+
+
+
+
+ Show {open ? 'less' : 'more'}
+
+
+ This is the collapsed content
+ This is the second paragraph
+ This is a longer sentence and also the third paragraph
+
+
+
+
+ );
+}
+
+const grey = {
+ 50: '#F3F6F9',
+ 100: '#E5EAF2',
+ 200: '#DAE2ED',
+ 300: '#C7D0DD',
+ 400: '#B0B8C4',
+ 500: '#9DA8B7',
+ 600: '#6B7A90',
+ 700: '#434D5B',
+ 800: '#303740',
+ 900: '#1C2025',
+};
+
+function useIsDarkMode() {
+ const theme = useTheme();
+ return theme.palette.mode === 'dark';
+}
+
+export function Styles() {
+ const isDarkMode = useIsDarkMode();
+ return (
+
+ );
+}
diff --git a/docs/data/base/components/collapsible/CssAnimatedCollapsible.tsx b/docs/data/base/components/collapsible/CssAnimatedCollapsible.tsx
new file mode 100644
index 0000000000..fe5e8fd2d9
--- /dev/null
+++ b/docs/data/base/components/collapsible/CssAnimatedCollapsible.tsx
@@ -0,0 +1,139 @@
+'use client';
+import * as React from 'react';
+import { useTheme } from '@mui/system';
+import * as Collapsible from '@base_ui/react/Collapsible';
+
+export default function CssAnimatedCollapsible() {
+ const [open, setOpen] = React.useState(true);
+ return (
+
+
+
+
+
+
+
+
+ Show {open ? 'less' : 'more'}
+
+
+ This is the collapsed content
+ This is the second paragraph
+ This is a longer sentence and also the third paragraph
+
+
+
+
+ );
+}
+
+const grey = {
+ 50: '#F3F6F9',
+ 100: '#E5EAF2',
+ 200: '#DAE2ED',
+ 300: '#C7D0DD',
+ 400: '#B0B8C4',
+ 500: '#9DA8B7',
+ 600: '#6B7A90',
+ 700: '#434D5B',
+ 800: '#303740',
+ 900: '#1C2025',
+};
+
+function useIsDarkMode() {
+ const theme = useTheme();
+ return theme.palette.mode === 'dark';
+}
+
+export function Styles() {
+ const isDarkMode = useIsDarkMode();
+ return (
+
+ );
+}
diff --git a/docs/data/base/components/collapsible/CssTransitionCollapsible.js b/docs/data/base/components/collapsible/CssTransitionCollapsible.js
new file mode 100644
index 0000000000..164082bb74
--- /dev/null
+++ b/docs/data/base/components/collapsible/CssTransitionCollapsible.js
@@ -0,0 +1,128 @@
+'use client';
+import * as React from 'react';
+import { useTheme } from '@mui/system';
+import * as Collapsible from '@base_ui/react/Collapsible';
+
+export default function CssTransitionCollapsible() {
+ const [open, setOpen] = React.useState(true);
+ return (
+
+
+
+
+
+
+
+
+ Show {open ? 'less' : 'more'}
+
+
+ This is the collapsed content
+ This is the second paragraph
+ This is a longer sentence and also the third paragraph
+
+
+
+
+ );
+}
+
+const grey = {
+ 50: '#F3F6F9',
+ 100: '#E5EAF2',
+ 200: '#DAE2ED',
+ 300: '#C7D0DD',
+ 400: '#B0B8C4',
+ 500: '#9DA8B7',
+ 600: '#6B7A90',
+ 700: '#434D5B',
+ 800: '#303740',
+ 900: '#1C2025',
+};
+
+function useIsDarkMode() {
+ const theme = useTheme();
+ return theme.palette.mode === 'dark';
+}
+
+export function Styles() {
+ const isDarkMode = useIsDarkMode();
+ return (
+
+ );
+}
diff --git a/docs/data/base/components/collapsible/CssTransitionCollapsible.tsx b/docs/data/base/components/collapsible/CssTransitionCollapsible.tsx
new file mode 100644
index 0000000000..164082bb74
--- /dev/null
+++ b/docs/data/base/components/collapsible/CssTransitionCollapsible.tsx
@@ -0,0 +1,128 @@
+'use client';
+import * as React from 'react';
+import { useTheme } from '@mui/system';
+import * as Collapsible from '@base_ui/react/Collapsible';
+
+export default function CssTransitionCollapsible() {
+ const [open, setOpen] = React.useState(true);
+ return (
+
+
+
+
+
+
+
+
+ Show {open ? 'less' : 'more'}
+
+
+ This is the collapsed content
+ This is the second paragraph
+ This is a longer sentence and also the third paragraph
+
+
+
+
+ );
+}
+
+const grey = {
+ 50: '#F3F6F9',
+ 100: '#E5EAF2',
+ 200: '#DAE2ED',
+ 300: '#C7D0DD',
+ 400: '#B0B8C4',
+ 500: '#9DA8B7',
+ 600: '#6B7A90',
+ 700: '#434D5B',
+ 800: '#303740',
+ 900: '#1C2025',
+};
+
+function useIsDarkMode() {
+ const theme = useTheme();
+ return theme.palette.mode === 'dark';
+}
+
+export function Styles() {
+ const isDarkMode = useIsDarkMode();
+ return (
+
+ );
+}
diff --git a/docs/data/base/components/collapsible/UnstyledCollapsibleIntroduction.js b/docs/data/base/components/collapsible/UnstyledCollapsibleIntroduction.js
new file mode 100644
index 0000000000..e829370f9c
--- /dev/null
+++ b/docs/data/base/components/collapsible/UnstyledCollapsibleIntroduction.js
@@ -0,0 +1,77 @@
+'use client';
+import * as React from 'react';
+import { styled, useTheme, Box } from '@mui/system';
+import * as BaseCollapsible from '@base_ui/react/Collapsible';
+
+const Collapsible = BaseCollapsible.Root;
+
+const CollapsibleTrigger = styled(BaseCollapsible.Trigger)`
+ display: flex;
+ flex-flow: row nowrap;
+ justify-content: center;
+ gap: 4px;
+ font-size: 16px;
+
+ & svg {
+ margin-top: 1px;
+ }
+
+ &[data-state='open'] svg {
+ transform: rotate(180deg);
+ }
+`;
+
+const CollapsibleContent = styled(BaseCollapsible.Content)``;
+
+export default function UnstyledCollapsibleIntroduction() {
+ // Replace this with your app logic for determining dark mode
+ const isDarkMode = useIsDarkMode();
+ const [open, setOpen] = React.useState(true);
+ return (
+
+
+
+
+
+
+ Show {open ? 'less' : 'more'}
+
+
+
+ This is the collapsed content. The element that shows and hides the
+ content has role button
+
+
+ When the content is visible, the element with role `button` has
+ `aria-expanded` set to `true`
+
+ When the content area is hidden, it is set to `false`
+
+ Optionally, the element with role `button` has a value specified for
+ `aria-controls` that refers to the element that contains all the content
+ that is shown or hidden
+
+
+
+
+ );
+}
+
+function useIsDarkMode() {
+ const theme = useTheme();
+ return theme.palette.mode === 'dark';
+}
diff --git a/docs/data/base/components/collapsible/UnstyledCollapsibleIntroduction.tsx b/docs/data/base/components/collapsible/UnstyledCollapsibleIntroduction.tsx
new file mode 100644
index 0000000000..e829370f9c
--- /dev/null
+++ b/docs/data/base/components/collapsible/UnstyledCollapsibleIntroduction.tsx
@@ -0,0 +1,77 @@
+'use client';
+import * as React from 'react';
+import { styled, useTheme, Box } from '@mui/system';
+import * as BaseCollapsible from '@base_ui/react/Collapsible';
+
+const Collapsible = BaseCollapsible.Root;
+
+const CollapsibleTrigger = styled(BaseCollapsible.Trigger)`
+ display: flex;
+ flex-flow: row nowrap;
+ justify-content: center;
+ gap: 4px;
+ font-size: 16px;
+
+ & svg {
+ margin-top: 1px;
+ }
+
+ &[data-state='open'] svg {
+ transform: rotate(180deg);
+ }
+`;
+
+const CollapsibleContent = styled(BaseCollapsible.Content)``;
+
+export default function UnstyledCollapsibleIntroduction() {
+ // Replace this with your app logic for determining dark mode
+ const isDarkMode = useIsDarkMode();
+ const [open, setOpen] = React.useState(true);
+ return (
+
+
+
+
+
+
+ Show {open ? 'less' : 'more'}
+
+
+
+ This is the collapsed content. The element that shows and hides the
+ content has role button
+
+
+ When the content is visible, the element with role `button` has
+ `aria-expanded` set to `true`
+
+ When the content area is hidden, it is set to `false`
+
+ Optionally, the element with role `button` has a value specified for
+ `aria-controls` that refers to the element that contains all the content
+ that is shown or hidden
+
+
+
+
+ );
+}
+
+function useIsDarkMode() {
+ const theme = useTheme();
+ return theme.palette.mode === 'dark';
+}
diff --git a/docs/data/base/components/collapsible/collapsible.md b/docs/data/base/components/collapsible/collapsible.md
new file mode 100644
index 0000000000..50f843d7d5
--- /dev/null
+++ b/docs/data/base/components/collapsible/collapsible.md
@@ -0,0 +1,220 @@
+---
+productId: base-ui
+title: React Collapsible components
+components: CollapsibleRoot, CollapsibleTrigger, CollapsibleContent
+hooks: useCollapsibleRoot, useCollapsibleTrigger, useCollapsibleContent
+githubLabel: 'component: collapsible'
+waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/
+packageName: '@base_ui/react'
+---
+
+# Collapsible
+
+Collapsible is a component that shows or hides content.
+
+{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}
+
+{{"component": "modules/components/ComponentPageTabs.js"}}
+
+{{"demo": "UnstyledCollapsibleIntroduction.js", "defaultCodeOpen": false, "bg": "gradient"}}
+
+## Installation
+
+Base UI components are all available as a single package.
+
+
+
+```bash npm
+npm install @base_ui/react
+```
+
+```bash yarn
+yarn add @base_ui/react
+```
+
+```bash pnpm
+pnpm add @base_ui/react
+```
+
+
+
+Once you have the package installed, import the component.
+
+```ts
+import * as Collapsible from '@base_ui/react/Collapsible';
+```
+
+## Anatomy
+
+- ` ` is a top-level component that facilitates communication between other components. It does not render to the DOM by default.
+- ` ` is the trigger element, a `` by default, that toggles the open/closed state of the content
+- ` ` is component that contains the Collapsible's content
+
+```js
+
+ Toggle
+ This is the content
+
+```
+
+## Improving searchability of hidden content
+
+:::warning
+This is [not yet supported](https://caniuse.com/mdn-html_global_attributes_hidden_until-found_value) in Safari and Firefox as of August 2024 and will fall back to the default `hidden` behavior.
+
+:::
+
+Content hidden in the `Collapsible.Content` component can be made accessible only to a browser's find-in-page functionality with the `htmlHidden` prop to improve searchability:
+
+```js
+
+ Toggle
+
+ When this component is closed, this sentence will only be accessible to the
+ browser's native find-in-page functionality
+
+
+```
+
+We recommend using [CSS animations](#css-animations) for animated collapsibles that use this feature. Currently there is browser bug that does not highlight the found text inside elements that have a [CSS transition](#css-transitions) applied.
+
+This relies on the HTML `hidden="until-found"` attribute which only has [partial browser support](https://caniuse.com/mdn-html_global_attributes_hidden_until-found_value) as of August 2024, but automatically falls back to the default `hidden` state in unsupported browsers.
+
+## Animations
+
+### Animation states
+
+Four states are available as data attributes to animate the Collapsible:
+
+- `[data-state="open"]` - `open` state is `true`.
+- `[data-state="closed"]` - `open` state is `false`. Can still be mounted to the DOM if closing.
+- `[data-entering]` - the `hidden` attribute was just removed from the DOM and the content element participates in page layout. The `data-entering` attribute will be removed 1 animation frame later.
+- `[data-exiting]` - the content element is in the process of being hidden from the DOM, but is still mounted.
+
+The component can be animate when opening or closing using either:
+
+- CSS animations
+- CSS transitions
+- JavaScript animations
+
+The height of the `Content` subcomponent is provided as the `--collapsible-content-height` CSS variable
+
+### CSS Animations
+
+CSS animations can be used with two declarations:
+
+```css
+.Collapsible-content {
+ overflow: hidden;
+}
+
+.Collapsible-content[data-state='open'] {
+ animation: slideDown 300ms ease-out;
+}
+
+.Collapsible-content[data-state='closed'] {
+ animation: slideUp 300ms ease-in;
+}
+
+@keyframes slideDown {
+ from {
+ height: 0;
+ }
+ to {
+ height: var(--collapsible-content-height);
+ }
+}
+
+@keyframes slideUp {
+ from {
+ height: var(--collapsible-content-height);
+ }
+ to {
+ height: 0;
+ }
+}
+```
+
+{{"demo": "CssAnimatedCollapsible.js"}}
+
+### CSS Transitions
+
+When using CSS transitions, styles for the `Content` subcomponent must be applied to three states:
+
+- The closed styles with `[data-state="closed"]`
+- The open styles with `[data-state="open"]`
+- The entering styles with `[data-entering]`
+
+```css
+.Collapsible-content {
+ overflow: hidden;
+}
+
+.Collapsible2-content[data-state='open'] {
+ height: var(--collapsible-content-height);
+ transition: height 300ms ease-out;
+}
+
+.Collapsible-content[data-entering] {
+ height: 0;
+}
+
+.Collapsible2-content[data-state='closed'] {
+ height: 0;
+ transition: height 300ms ease-in;
+}
+```
+
+{{"demo": "CssTransitionCollapsible.js"}}
+
+### JavaScript Animations
+
+When using external libraries for animation, for example `framer-motion`, be aware that Collapsible hides content using the html `hidden` attribute in the closed state, and does not unmount the `Collapsible.Content` subcomponent.
+
+```js
+function App() {
+ const [open, setOpen] = useState(false);
+ return (
+
+ Toggle
+
+ }
+ >
+ This is the content
+
+
+ );
+}
+```
+
+## Overriding default components
+
+Use the `render` prop to override the rendered elements with your own components. The `Collapsible.Root` component does not render an element to the DOM by default, but can do so with the render prop:
+
+```jsx
+// Element shorthand
+ } />
+```
+
+```jsx
+// Function
+ } />
+```
diff --git a/docs/data/base/components/dialog/DialogWithTransitions.js b/docs/data/base/components/dialog/DialogWithTransitions.js
new file mode 100644
index 0000000000..901f682060
--- /dev/null
+++ b/docs/data/base/components/dialog/DialogWithTransitions.js
@@ -0,0 +1,135 @@
+import * as React from 'react';
+import * as BaseDialog from '@base_ui/react/Dialog';
+import { styled } from '@mui/system';
+
+export default function DialogWithTransitions() {
+ return (
+
+ Open
+
+ Animated dialog
+
+ This dialog uses CSS transitions on entry and exit.
+
+
+ Close
+
+
+
+
+ );
+}
+
+const grey = {
+ 900: '#0f172a',
+ 800: '#1e293b',
+ 700: '#334155',
+ 500: '#64748b',
+ 300: '#cbd5e1',
+ 200: '#e2e8f0',
+ 100: '#f1f5f9',
+ 50: '#f8fafc',
+};
+
+const Popup = styled(BaseDialog.Popup)(
+ ({ theme }) => `
+ background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
+ min-width: 400px;
+ border-radius: 4px;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ font-family: IBM Plex Sans;
+ padding: 16px;
+ z-index: 2100;
+ transition-property: opacity, transform;
+ transition-duration: 150ms;
+ transition-timing-function: ease-in;
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+
+ &[data-state='open'] {
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(1);
+ transition-timing-function: ease-out;
+ }
+
+ &[data-entering] {
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+ }
+`,
+);
+
+const Backdrop = styled(BaseDialog.Backdrop)`
+ background-color: rgb(0 0 0 / 0.2);
+ position: fixed;
+ inset: 0;
+ z-index: 2000;
+ backdrop-filter: blur(0);
+ opacity: 0;
+ transition-property: opacity, backdrop-filter;
+ transition-duration: 250ms;
+ transition-timing-function: ease-in;
+
+ &[data-state='open'] {
+ backdrop-filter: blur(6px);
+ opacity: 1;
+ transition-timing-function: ease-out;
+ }
+
+ &[data-entering] {
+ backdrop-filter: blur(0);
+ opacity: 0;
+ }
+`;
+
+const Title = styled(BaseDialog.Title)`
+ font-size: 1.25rem;
+`;
+
+const Trigger = styled(BaseDialog.Trigger)(
+ ({ theme }) => `
+ background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ border: none;
+ font-family:
+ "IBM Plex Sans",
+ sans-serif;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
+ }
+`,
+);
+
+const Close = styled(BaseDialog.Close)(
+ ({ theme }) => `
+ background-color: transparent;
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
+ color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ font-family: IBM Plex Sans, sans-serif;
+ min-width: 80px;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ }
+`,
+);
+
+const Controls = styled('div')(
+ ({ theme }) => `
+ display: flex;
+ flex-direction: row-reverse;
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ gap: 8px;
+ padding: 16px;
+ margin: 32px -16px -16px;
+`,
+);
diff --git a/docs/data/base/components/dialog/DialogWithTransitions.tsx b/docs/data/base/components/dialog/DialogWithTransitions.tsx
new file mode 100644
index 0000000000..901f682060
--- /dev/null
+++ b/docs/data/base/components/dialog/DialogWithTransitions.tsx
@@ -0,0 +1,135 @@
+import * as React from 'react';
+import * as BaseDialog from '@base_ui/react/Dialog';
+import { styled } from '@mui/system';
+
+export default function DialogWithTransitions() {
+ return (
+
+ Open
+
+ Animated dialog
+
+ This dialog uses CSS transitions on entry and exit.
+
+
+ Close
+
+
+
+
+ );
+}
+
+const grey = {
+ 900: '#0f172a',
+ 800: '#1e293b',
+ 700: '#334155',
+ 500: '#64748b',
+ 300: '#cbd5e1',
+ 200: '#e2e8f0',
+ 100: '#f1f5f9',
+ 50: '#f8fafc',
+};
+
+const Popup = styled(BaseDialog.Popup)(
+ ({ theme }) => `
+ background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
+ min-width: 400px;
+ border-radius: 4px;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ font-family: IBM Plex Sans;
+ padding: 16px;
+ z-index: 2100;
+ transition-property: opacity, transform;
+ transition-duration: 150ms;
+ transition-timing-function: ease-in;
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+
+ &[data-state='open'] {
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(1);
+ transition-timing-function: ease-out;
+ }
+
+ &[data-entering] {
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+ }
+`,
+);
+
+const Backdrop = styled(BaseDialog.Backdrop)`
+ background-color: rgb(0 0 0 / 0.2);
+ position: fixed;
+ inset: 0;
+ z-index: 2000;
+ backdrop-filter: blur(0);
+ opacity: 0;
+ transition-property: opacity, backdrop-filter;
+ transition-duration: 250ms;
+ transition-timing-function: ease-in;
+
+ &[data-state='open'] {
+ backdrop-filter: blur(6px);
+ opacity: 1;
+ transition-timing-function: ease-out;
+ }
+
+ &[data-entering] {
+ backdrop-filter: blur(0);
+ opacity: 0;
+ }
+`;
+
+const Title = styled(BaseDialog.Title)`
+ font-size: 1.25rem;
+`;
+
+const Trigger = styled(BaseDialog.Trigger)(
+ ({ theme }) => `
+ background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ border: none;
+ font-family:
+ "IBM Plex Sans",
+ sans-serif;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
+ }
+`,
+);
+
+const Close = styled(BaseDialog.Close)(
+ ({ theme }) => `
+ background-color: transparent;
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
+ color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ font-family: IBM Plex Sans, sans-serif;
+ min-width: 80px;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ }
+`,
+);
+
+const Controls = styled('div')(
+ ({ theme }) => `
+ display: flex;
+ flex-direction: row-reverse;
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ gap: 8px;
+ padding: 16px;
+ margin: 32px -16px -16px;
+`,
+);
diff --git a/docs/data/base/components/dialog/DialogWithTransitions.tsx.preview b/docs/data/base/components/dialog/DialogWithTransitions.tsx.preview
new file mode 100644
index 0000000000..15e757d86a
--- /dev/null
+++ b/docs/data/base/components/dialog/DialogWithTransitions.tsx.preview
@@ -0,0 +1,13 @@
+
+ Open
+
+ Animated dialog
+
+ This dialog uses CSS transitions on entry and exit.
+
+
+ Close
+
+
+
+
\ No newline at end of file
diff --git a/docs/data/base/components/dialog/NestedDialogs.js b/docs/data/base/components/dialog/NestedDialogs.js
new file mode 100644
index 0000000000..3a42cef5d3
--- /dev/null
+++ b/docs/data/base/components/dialog/NestedDialogs.js
@@ -0,0 +1,163 @@
+import * as React from 'react';
+import * as BaseDialog from '@base_ui/react/Dialog';
+import { styled } from '@mui/system';
+
+export default function NestedDialogs() {
+ return (
+
+ Open
+
+
+ Dialog 1
+
+
+ Open Nested
+
+
+ Dialog 2
+
+
+ Open Nested
+
+
+ Dialog 3
+
+ Close
+
+
+
+ Close
+
+
+
+ Close
+
+
+
+ );
+}
+
+const grey = {
+ 900: '#0f172a',
+ 800: '#1e293b',
+ 700: '#334155',
+ 500: '#64748b',
+ 300: '#cbd5e1',
+ 200: '#e2e8f0',
+ 100: '#f1f5f9',
+ 50: '#f8fafc',
+};
+
+const Popup = styled(BaseDialog.Popup)(
+ ({ theme }) => `
+ --transition-duration: 150ms;
+ background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
+ min-width: 400px;
+ border-radius: 4px;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ font-family: IBM Plex Sans;
+ padding: 16px;
+ z-index: 2100;
+ transform: translate(-50%, -35%) scale(0.8, calc(pow(0.95, var(--nested-dialogs))))
+ translateY(calc(-30px * var(--nested-dialogs)));
+ visibility: hidden;
+ opacity: 0.5;
+ transition:
+ transform var(--transition-duration) ease-in,
+ opacity var(--transition-duration) ease-in,
+ visibility var(--transition-duration) step-end;
+
+ &[data-state='open'] {
+ @starting-style {
+ & {
+ transform: translate(-50%, -35%) scale(0.8) translateY(0);
+ opacity: 0.5;
+ }
+ }
+
+ visibility: visible;
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(calc(pow(0.95, var(--nested-dialogs))))
+ translateY(calc(-30px * var(--nested-dialogs)));
+ transition:
+ transform var(--transition-duration) ease-out,
+ opacity var(--transition-duration) ease-out,
+ visibility var(--transition-duration) step-start;
+ }
+`,
+);
+
+const Title = styled(BaseDialog.Title)`
+ font-size: 1.25rem;
+`;
+
+const Trigger = styled(BaseDialog.Trigger)(
+ ({ theme }) => `
+ background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ border: none;
+ font-family:
+ "IBM Plex Sans",
+ sans-serif;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
+ }
+`,
+);
+
+const Close = styled(BaseDialog.Close)(
+ ({ theme }) => `
+ background-color: transparent;
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
+ color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ font-family: IBM Plex Sans, sans-serif;
+ min-width: 80px;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ }
+`,
+);
+
+const Backdrop = styled(BaseDialog.Backdrop)`
+ background-color: rgb(0 0 0 / 0.2);
+ position: fixed;
+ inset: 0;
+ z-index: 2000;
+ backdrop-filter: blur(0);
+ opacity: 0;
+ transition-property: opacity, backdrop-filter;
+ transition-duration: 250ms;
+ transition-timing-function: ease-in;
+
+ &[data-state='open'] {
+ backdrop-filter: blur(6px);
+ opacity: 1;
+ transition-timing-function: ease-out;
+ }
+
+ &[data-entering] {
+ backdrop-filter: blur(0);
+ opacity: 0;
+ }
+`;
+
+const Controls = styled('div')(
+ ({ theme }) => `
+ display: flex;
+ flex-direction: row-reverse;
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ gap: 8px;
+ padding: 16px;
+ margin: 32px -16px -16px;
+`,
+);
diff --git a/docs/data/base/components/dialog/NestedDialogs.tsx b/docs/data/base/components/dialog/NestedDialogs.tsx
new file mode 100644
index 0000000000..3a42cef5d3
--- /dev/null
+++ b/docs/data/base/components/dialog/NestedDialogs.tsx
@@ -0,0 +1,163 @@
+import * as React from 'react';
+import * as BaseDialog from '@base_ui/react/Dialog';
+import { styled } from '@mui/system';
+
+export default function NestedDialogs() {
+ return (
+
+ Open
+
+
+ Dialog 1
+
+
+ Open Nested
+
+
+ Dialog 2
+
+
+ Open Nested
+
+
+ Dialog 3
+
+ Close
+
+
+
+ Close
+
+
+
+ Close
+
+
+
+ );
+}
+
+const grey = {
+ 900: '#0f172a',
+ 800: '#1e293b',
+ 700: '#334155',
+ 500: '#64748b',
+ 300: '#cbd5e1',
+ 200: '#e2e8f0',
+ 100: '#f1f5f9',
+ 50: '#f8fafc',
+};
+
+const Popup = styled(BaseDialog.Popup)(
+ ({ theme }) => `
+ --transition-duration: 150ms;
+ background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
+ min-width: 400px;
+ border-radius: 4px;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ font-family: IBM Plex Sans;
+ padding: 16px;
+ z-index: 2100;
+ transform: translate(-50%, -35%) scale(0.8, calc(pow(0.95, var(--nested-dialogs))))
+ translateY(calc(-30px * var(--nested-dialogs)));
+ visibility: hidden;
+ opacity: 0.5;
+ transition:
+ transform var(--transition-duration) ease-in,
+ opacity var(--transition-duration) ease-in,
+ visibility var(--transition-duration) step-end;
+
+ &[data-state='open'] {
+ @starting-style {
+ & {
+ transform: translate(-50%, -35%) scale(0.8) translateY(0);
+ opacity: 0.5;
+ }
+ }
+
+ visibility: visible;
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(calc(pow(0.95, var(--nested-dialogs))))
+ translateY(calc(-30px * var(--nested-dialogs)));
+ transition:
+ transform var(--transition-duration) ease-out,
+ opacity var(--transition-duration) ease-out,
+ visibility var(--transition-duration) step-start;
+ }
+`,
+);
+
+const Title = styled(BaseDialog.Title)`
+ font-size: 1.25rem;
+`;
+
+const Trigger = styled(BaseDialog.Trigger)(
+ ({ theme }) => `
+ background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ border: none;
+ font-family:
+ "IBM Plex Sans",
+ sans-serif;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
+ }
+`,
+);
+
+const Close = styled(BaseDialog.Close)(
+ ({ theme }) => `
+ background-color: transparent;
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
+ color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ font-family: IBM Plex Sans, sans-serif;
+ min-width: 80px;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ }
+`,
+);
+
+const Backdrop = styled(BaseDialog.Backdrop)`
+ background-color: rgb(0 0 0 / 0.2);
+ position: fixed;
+ inset: 0;
+ z-index: 2000;
+ backdrop-filter: blur(0);
+ opacity: 0;
+ transition-property: opacity, backdrop-filter;
+ transition-duration: 250ms;
+ transition-timing-function: ease-in;
+
+ &[data-state='open'] {
+ backdrop-filter: blur(6px);
+ opacity: 1;
+ transition-timing-function: ease-out;
+ }
+
+ &[data-entering] {
+ backdrop-filter: blur(0);
+ opacity: 0;
+ }
+`;
+
+const Controls = styled('div')(
+ ({ theme }) => `
+ display: flex;
+ flex-direction: row-reverse;
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ gap: 8px;
+ padding: 16px;
+ margin: 32px -16px -16px;
+`,
+);
diff --git a/docs/data/base/components/dialog/UnstyledDialogIntroduction/css/index.js b/docs/data/base/components/dialog/UnstyledDialogIntroduction/css/index.js
new file mode 100644
index 0000000000..f50a332498
--- /dev/null
+++ b/docs/data/base/components/dialog/UnstyledDialogIntroduction/css/index.js
@@ -0,0 +1,133 @@
+import * as React from 'react';
+import * as Dialog from '@base_ui/react/Dialog';
+import { useTheme } from '@mui/system';
+
+export default function UnstyledDialogIntroduction() {
+ return (
+
+
+ Subscribe
+
+
+ Subscribe
+
+ Enter your email address to subscribe to our newsletter.
+
+
+
+ Subscribe
+ Cancel
+
+
+
+
+
+ );
+}
+
+function useIsDarkMode() {
+ const theme = useTheme();
+ return theme.palette.mode === 'dark';
+}
+
+const grey = {
+ 900: '#0f172a',
+ 800: '#1e293b',
+ 700: '#334155',
+ 500: '#64748b',
+ 300: '#cbd5e1',
+ 200: '#e2e8f0',
+ 100: '#f1f5f9',
+ 50: '#f8fafc',
+};
+
+function Styles() {
+ // Replace this with your app logic for determining dark mode
+ const isDarkMode = useIsDarkMode();
+
+ return (
+
+ );
+}
diff --git a/docs/data/base/components/dialog/UnstyledDialogIntroduction/css/index.tsx b/docs/data/base/components/dialog/UnstyledDialogIntroduction/css/index.tsx
new file mode 100644
index 0000000000..f50a332498
--- /dev/null
+++ b/docs/data/base/components/dialog/UnstyledDialogIntroduction/css/index.tsx
@@ -0,0 +1,133 @@
+import * as React from 'react';
+import * as Dialog from '@base_ui/react/Dialog';
+import { useTheme } from '@mui/system';
+
+export default function UnstyledDialogIntroduction() {
+ return (
+
+
+ Subscribe
+
+
+ Subscribe
+
+ Enter your email address to subscribe to our newsletter.
+
+
+
+ Subscribe
+ Cancel
+
+
+
+
+
+ );
+}
+
+function useIsDarkMode() {
+ const theme = useTheme();
+ return theme.palette.mode === 'dark';
+}
+
+const grey = {
+ 900: '#0f172a',
+ 800: '#1e293b',
+ 700: '#334155',
+ 500: '#64748b',
+ 300: '#cbd5e1',
+ 200: '#e2e8f0',
+ 100: '#f1f5f9',
+ 50: '#f8fafc',
+};
+
+function Styles() {
+ // Replace this with your app logic for determining dark mode
+ const isDarkMode = useIsDarkMode();
+
+ return (
+
+ );
+}
diff --git a/docs/data/base/components/dialog/UnstyledDialogIntroduction/system/index.js b/docs/data/base/components/dialog/UnstyledDialogIntroduction/system/index.js
new file mode 100644
index 0000000000..f52c7b9977
--- /dev/null
+++ b/docs/data/base/components/dialog/UnstyledDialogIntroduction/system/index.js
@@ -0,0 +1,121 @@
+import * as React from 'react';
+import * as Dialog from '@base_ui/react/Dialog';
+import { styled } from '@mui/system';
+
+export default function UnstyledDialogIntroduction() {
+ return (
+
+ Subscribe
+
+
+ Subscribe
+
+ Enter your email address to subscribe to our newsletter.
+
+
+
+ Subscribe
+ Cancel
+
+
+
+ );
+}
+
+const grey = {
+ 900: '#0f172a',
+ 800: '#1e293b',
+ 700: '#334155',
+ 500: '#64748b',
+ 300: '#cbd5e1',
+ 200: '#e2e8f0',
+ 100: '#f1f5f9',
+ 50: '#f8fafc',
+};
+
+const TriggerButton = styled(Dialog.Trigger)(
+ ({ theme }) => `
+ background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ border: none;
+ font-family: "IBM Plex Sans", sans-serif;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
+ }
+`,
+);
+
+const Popup = styled(Dialog.Popup)(
+ ({ theme }) => `
+ background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
+ min-width: 400px;
+ border-radius: 4px;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ font-family: "IBM Plex Sans", sans-serif;
+ transform: translate(-50%, -50%);
+ padding: 16px;
+ z-index: 2100;
+`,
+);
+
+const Controls = styled('div')(
+ ({ theme }) => `
+ display: flex;
+ flex-direction: row-reverse;
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ gap: 8px;
+ padding: 16px;
+ margin: 32px -16px -16px;
+`,
+);
+
+const CloseButton = styled(Dialog.Close)(
+ ({ theme }) => `
+ background-color: transparent;
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
+ color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ font-family: "IBM Plex Sans", sans-serif;
+ min-width: 80px;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ }
+`,
+);
+
+const Title = styled(Dialog.Title)`
+ font-size: 1.25rem;
+`;
+
+const Description = styled(Dialog.Description)``;
+
+const Backdrop = styled(Dialog.Backdrop)`
+ background: rgb(0 0 0 / 0.35);
+ position: fixed;
+ inset: 0;
+ backdrop-filter: blur(4px);
+ z-index: 2000;
+`;
+
+const TextField = styled('input')`
+ padding: 8px;
+ border-radius: 4px;
+ border: 1px solid ${grey[300]};
+ font-family: 'IBM Plex Sans', sans-serif;
+ margin: 16px 0;
+ width: 100%;
+ box-sizing: border-box;
+`;
diff --git a/docs/data/base/components/dialog/UnstyledDialogIntroduction/system/index.tsx b/docs/data/base/components/dialog/UnstyledDialogIntroduction/system/index.tsx
new file mode 100644
index 0000000000..f52c7b9977
--- /dev/null
+++ b/docs/data/base/components/dialog/UnstyledDialogIntroduction/system/index.tsx
@@ -0,0 +1,121 @@
+import * as React from 'react';
+import * as Dialog from '@base_ui/react/Dialog';
+import { styled } from '@mui/system';
+
+export default function UnstyledDialogIntroduction() {
+ return (
+
+ Subscribe
+
+
+ Subscribe
+
+ Enter your email address to subscribe to our newsletter.
+
+
+
+ Subscribe
+ Cancel
+
+
+
+ );
+}
+
+const grey = {
+ 900: '#0f172a',
+ 800: '#1e293b',
+ 700: '#334155',
+ 500: '#64748b',
+ 300: '#cbd5e1',
+ 200: '#e2e8f0',
+ 100: '#f1f5f9',
+ 50: '#f8fafc',
+};
+
+const TriggerButton = styled(Dialog.Trigger)(
+ ({ theme }) => `
+ background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ border: none;
+ font-family: "IBM Plex Sans", sans-serif;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]};
+ }
+`,
+);
+
+const Popup = styled(Dialog.Popup)(
+ ({ theme }) => `
+ background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]};
+ min-width: 400px;
+ border-radius: 4px;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ font-family: "IBM Plex Sans", sans-serif;
+ transform: translate(-50%, -50%);
+ padding: 16px;
+ z-index: 2100;
+`,
+);
+
+const Controls = styled('div')(
+ ({ theme }) => `
+ display: flex;
+ flex-direction: row-reverse;
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ gap: 8px;
+ padding: 16px;
+ margin: 32px -16px -16px;
+`,
+);
+
+const CloseButton = styled(Dialog.Close)(
+ ({ theme }) => `
+ background-color: transparent;
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
+ color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]};
+ padding: 8px 16px;
+ border-radius: 4px;
+ font-family: "IBM Plex Sans", sans-serif;
+ min-width: 80px;
+
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ }
+`,
+);
+
+const Title = styled(Dialog.Title)`
+ font-size: 1.25rem;
+`;
+
+const Description = styled(Dialog.Description)``;
+
+const Backdrop = styled(Dialog.Backdrop)`
+ background: rgb(0 0 0 / 0.35);
+ position: fixed;
+ inset: 0;
+ backdrop-filter: blur(4px);
+ z-index: 2000;
+`;
+
+const TextField = styled('input')`
+ padding: 8px;
+ border-radius: 4px;
+ border: 1px solid ${grey[300]};
+ font-family: 'IBM Plex Sans', sans-serif;
+ margin: 16px 0;
+ width: 100%;
+ box-sizing: border-box;
+`;
diff --git a/docs/data/base/components/dialog/UnstyledDialogIntroduction/tailwind/index.js b/docs/data/base/components/dialog/UnstyledDialogIntroduction/tailwind/index.js
new file mode 100644
index 0000000000..ddcd4a4dc3
--- /dev/null
+++ b/docs/data/base/components/dialog/UnstyledDialogIntroduction/tailwind/index.js
@@ -0,0 +1,87 @@
+import * as React from 'react';
+import * as Dialog from '@base_ui/react/Dialog';
+
+export default function UnstyledDialogIntroduction() {
+ return (
+
+ Subscribe
+
+
+ Subscribe
+
+ Enter your email address to subscribe to our newsletter.
+
+
+
+ Subscribe
+ Cancel
+
+
+
+ );
+}
+
+function TriggerButton(props) {
+ const className = `
+ bg-slate-900 dark:bg-slate-50 text-slate-50 dark:text-slate-900
+ py-2 px-4 rounded min-w-[80px] border-none font-sans
+ hover:bg-slate-700 dark:hover:bg-slate-200`;
+
+ return ;
+}
+
+function Popup(props) {
+ const className = `
+ bg-slate-50 dark:bg-slate-900 border-[1px] border-solid border-slate-100 dark:border-slate-700
+ min-w-[400px] rounded shadow-xl fixed top-2/4 left-2/4 z-[2100]
+ -translate-x-2/4 -translate-y-2/4 p-4`;
+
+ return ;
+}
+
+function Controls(props) {
+ return (
+
+ );
+}
+
+function CloseButton(props) {
+ const className = `
+ bg-transparent border-[1px] border-solid border-slate-500 dark:border-slate-300
+ text-slate-900 dark:text-slate-50 py-2 px-4 rounded font-sans min-w-[80px]
+ hover:bg-slate-200 dark:hover:bg-slate-700`;
+
+ return ;
+}
+
+function Title(props) {
+ return ;
+}
+
+function Description(props) {
+ return ;
+}
+
+function Backdrop(props) {
+ return (
+
+ );
+}
+
+function TextField(props) {
+ const className = `
+ w-full p-2 mt-4 font-sans
+ border-[1px] border-solid border-slate-300 dark:border-slate-700 rounded
+ `;
+ return ;
+}
diff --git a/docs/data/base/components/dialog/UnstyledDialogIntroduction/tailwind/index.tsx b/docs/data/base/components/dialog/UnstyledDialogIntroduction/tailwind/index.tsx
new file mode 100644
index 0000000000..ecae78b79e
--- /dev/null
+++ b/docs/data/base/components/dialog/UnstyledDialogIntroduction/tailwind/index.tsx
@@ -0,0 +1,87 @@
+import * as React from 'react';
+import * as Dialog from '@base_ui/react/Dialog';
+
+export default function UnstyledDialogIntroduction() {
+ return (
+
+ Subscribe
+
+
+ Subscribe
+
+ Enter your email address to subscribe to our newsletter.
+
+
+
+ Subscribe
+ Cancel
+
+
+
+ );
+}
+
+function TriggerButton(props: Dialog.TriggerProps) {
+ const className = `
+ bg-slate-900 dark:bg-slate-50 text-slate-50 dark:text-slate-900
+ py-2 px-4 rounded min-w-[80px] border-none font-sans
+ hover:bg-slate-700 dark:hover:bg-slate-200`;
+
+ return ;
+}
+
+function Popup(props: Dialog.PopupProps) {
+ const className = `
+ bg-slate-50 dark:bg-slate-900 border-[1px] border-solid border-slate-100 dark:border-slate-700
+ min-w-[400px] rounded shadow-xl fixed top-2/4 left-2/4 z-[2100]
+ -translate-x-2/4 -translate-y-2/4 p-4`;
+
+ return ;
+}
+
+function Controls(props: React.ComponentPropsWithoutRef<'div'>) {
+ return (
+
+ );
+}
+
+function CloseButton(props: Dialog.CloseProps) {
+ const className = `
+ bg-transparent border-[1px] border-solid border-slate-500 dark:border-slate-300
+ text-slate-900 dark:text-slate-50 py-2 px-4 rounded font-sans min-w-[80px]
+ hover:bg-slate-200 dark:hover:bg-slate-700`;
+
+ return ;
+}
+
+function Title(props: Dialog.TitleProps) {
+ return ;
+}
+
+function Description(props: Dialog.DescriptionProps) {
+ return ;
+}
+
+function Backdrop(props: Dialog.BackdropProps) {
+ return (
+
+ );
+}
+
+function TextField(props: React.ComponentPropsWithoutRef<'input'>) {
+ const className = `
+ w-full p-2 mt-4 font-sans
+ border-[1px] border-solid border-slate-300 dark:border-slate-700 rounded
+ `;
+ return ;
+}
diff --git a/docs/data/base/components/dialog/dialog.md b/docs/data/base/components/dialog/dialog.md
new file mode 100644
index 0000000000..4f53472391
--- /dev/null
+++ b/docs/data/base/components/dialog/dialog.md
@@ -0,0 +1,320 @@
+---
+productId: base-ui
+title: React Dialog component and hook
+components: DialogBackdrop, DialogClose, DialogDescription, DialogPopup, DialogRoot, DialogTitle, DialogTrigger
+hooks: useDialogClose, useDialogPopup, useDialogRoot, useDialogTrigger
+githubLabel: 'component: dialog'
+waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
+---
+
+# Dialog
+
+Dialogs inform users about a task and can contain critical information, require decisions, or involve multiple tasks.
+
+{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}
+
+{{"component": "modules/components/ComponentPageTabs.js"}}
+
+{{"demo": "UnstyledDialogIntroduction", "defaultCodeOpen": false, "bg": "gradient"}}
+
+## Installation
+
+Base UI components are all available as a single package.
+
+
+
+```bash npm
+npm install @base_ui/react
+```
+
+```bash yarn
+yarn add @base_ui/react
+```
+
+```bash pnpm
+pnpm add @base_ui/react
+```
+
+
+
+Once you have the package installed, import the component.
+
+```ts
+import * as Dialog from '@base_ui/react/Dialog';
+```
+
+## Anatomy
+
+Dialogs are implemented using a collection of related components:
+
+- ` ` is a top-level component that facilitates communication between other components. It does not render to the DOM.
+- ` ` is the dialog panel itself.
+- ` ` is the background element appearing when a popup is visible. Use it to indicate that the page is inert when using a modal dialog. The Backdrop must be a sibling of the Popup component. It is mandatory for modal dialogs.
+- ` ` is the component (a button by default) that, when clicked, shows the popup. When it's not provided, the visibility of the Dialog can be controlled with its `open` prop (see [Controlled vs. uncontrolled behavior](#controlled-vs-uncontrolled-behavior)).
+- ` ` renders a button that closes the popup. You can attach your own click handlers to it to perform additional actions.
+- ` ` is an header element displaying the title of the dialog. It is referenced in the Dialog's ARIA attributes to properly announce the dialog.
+- ` ` is an element describing of the dialog. It is referenced in the Dialog's ARIA attributes to properly announce the dialog.
+
+```tsx
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Modal and non-modal dialogs
+
+Dialogs can be either modal (rendering the rest of the page inert) or non-modal.
+A non-modal dialog can be used to implement tool windows.
+
+The `modal` prop of the `` controls this.
+By default Dialogs are modal.
+
+```tsx
+{/* ... */}
+```
+
+:::warning
+To make the Dialog fully modal, you must have a Backdrop component and style it so it covers the entire viewport, blocking pointer interaction with other elements on the page.
+:::
+
+## Closing the dialog
+
+The default way to close the dialog is clicking on the `` component.
+Dialogs also close when the user clicks outside of them or presses the Esc key.
+
+Closing on outside click can be disabled with a `dismissible` prop on the `Dialog.Root`:
+
+```tsx
+{/* ... */}
+```
+
+## Controlled vs. uncontrolled behavior
+
+The simplest way to control the visibility of the dialog is to use the `` and `` components.
+
+You can set the initial state with the `defaultOpen` prop.
+
+```tsx
+
+ Open
+
+ Demo dialog
+ Close
+
+
+```
+
+Doing so ensures that the accessibity attributes are set correctly so that the trigger button is approriately announced by assistive technologies.
+
+If you need to control the visibility programmatically from the outside, use the `value` prop.
+You can still use the `` and `` components (though it's not necessary), but you need to make sure to create a handler for the `onOpenChange` event and update the state manually.
+
+```tsx
+const [open, setOpen] = React.useState(false);
+
+return (
+
+ Open
+
+ Demo dialog
+ Close
+
+
+);
+```
+
+## Nested dialogs
+
+A dialog can open another dialog.
+At times, it may be useful to know how may open sub-dialogs a given dialog has.
+One example of this could be styling the bottom dialog in a way they appear below the top-most one.
+
+The number of open child dialogs is present in the `data-nested-dialogs` attribute and in the `--nested-dialogs` CSS variable on the `` component.
+
+{{"demo": "NestedDialogs.js"}}
+
+Note that when dialogs are nested, only the bottom-most backdrop is rendered.
+
+## Animation
+
+The `` and `` components support transitions on entry and exit.
+
+CSS animations and transitions are supported out of the box.
+If a component has a transition or animation applied to it when it closes, it will be unmounted only after the animation finishes.
+
+As this detection of exit animations requires an extra render, you may opt out of it by setting the `animated` prop on Root to `false`.
+We also recommend doing so in automated tests, to avoid asynchronous behavior and make testing easier.
+
+Alternatively, you can use JavaScript-based animations with a library like framer-motion, React Spring, or similar.
+With this approach set the `keepMounted` to `true` and let the animation library control mounting and unmounting.
+
+### CSS transitions
+
+Here is an example of how to apply a symmetric scale and fade transition with the default conditionally-rendered behavior:
+
+```jsx
+Dialog
+```
+
+```css
+.DialogPopup {
+ transition-property: opacity, transform;
+ transition-duration: 0.2s;
+ /* Represents the final styles once exited */
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+}
+
+/* Represents the final styles once entered */
+.DialogPopup[data-state='open'] {
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(1);
+}
+
+/* Represents the initial styles when entering */
+.DialogPopup[data-entering] {
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+}
+```
+
+Styles need to be applied in three states:
+
+- The exiting styles, placed on the base element class
+- The open styles, placed on the base element class with `[data-state="open"]`
+- The entering styles, placed on the base element class with `[data-entering]`
+
+{{"demo": "DialogWithTransitions.js"}}
+
+In newer browsers, there is a feature called `@starting-style` which allows transitions to occur on open for conditionally-mounted components:
+
+```css
+/* Base UI API - Polyfill */
+.DialogPopup[data-entering] {
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+}
+
+/* Official Browser API - no Firefox support as of May 2024 */
+@starting-style {
+ .DialogPopup[data-state='open'] {
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+ }
+}
+```
+
+### CSS animations
+
+CSS animations can also be used, requiring only two separate declarations:
+
+```css
+@keyframes scale-in {
+ from {
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+ }
+}
+
+@keyframes scale-out {
+ to {
+ opacity: 0;
+ transform: translate(-50%, -35%) scale(0.8);
+ }
+}
+
+.DialogPopup {
+ animation: scale-in 0.2s forwards;
+}
+
+.DialogPopup[data-exiting] {
+ animation: scale-out 0.2s forwards;
+}
+```
+
+### JavaScript animations
+
+The `keepMounted` prop lets an external library control the mounting, for example `framer-motion`'s `AnimatePresence` component.
+
+```js
+function App() {
+ const [open, setOpen] = useState(false);
+ return (
+
+ Trigger
+
+ {open && (
+
+ }
+ >
+ Dialog
+
+ )}
+
+
+ );
+}
+```
+
+### Animation states
+
+Four states are available as data attributes to animate the dialog, which enables full control depending on whether the popup is being animated with CSS transitions or animations, JavaScript, or is using the `keepMounted` prop.
+
+- `[data-state="open"]` - `open` state is `true`.
+- `[data-state="closed"]` - `open` state is `false`. Can still be mounted to the DOM if closing.
+- `[data-entering]` - the popup was just inserted to the DOM. The attribute is removed 1 animation frame later. Enables "starting styles" upon insertion for conditional rendering.
+- `[data-exiting]` - the popup is in the process of being removed from the DOM, but is still mounted.
+
+## Composing a custom React component
+
+Use the `render` prop to override the rendered element:
+
+```jsx
+ } />
+// or
+ } />
+```
+
+## Accessibility
+
+Using the `` sets the required accessibility attributes on the trigger button.
+If you prefer controlling the open state differently, you need to apply these attributes on your own:
+
+```tsx
+const [open, setOpen] = React.useState(false);
+
+return (
+
+ setOpen(true)}
+ >
+ Open
+
+
+
+
+
+
+);
+```
diff --git a/docs/data/base/components/drawer/drawer.md b/docs/data/base/components/drawer/drawer.md
deleted file mode 100644
index 92f3253ea7..0000000000
--- a/docs/data/base/components/drawer/drawer.md
+++ /dev/null
@@ -1,13 +0,0 @@
----
-productId: base-ui
-title: React Drawer component
-githubLabel: 'component: drawer'
----
-
-# Drawer 🚧
-
-Navigation drawers (also known as sidebars) provide ergonomic access to different destinations without taking the user out of context.
-
-:::warning
-The Base UI Drawer component isn't available yet, but you can upvote [this GitHub issue](https://github.com/mui/base-ui/issues/38) to see it arrive sooner.
-:::
diff --git a/docs/data/base/components/field/UnstyledFieldAsync.js b/docs/data/base/components/field/UnstyledFieldAsync.js
new file mode 100644
index 0000000000..d4c73250c8
--- /dev/null
+++ b/docs/data/base/components/field/UnstyledFieldAsync.js
@@ -0,0 +1,149 @@
+import * as React from 'react';
+import * as Field from '@base_ui/react/Field';
+import { styled } from '@mui/system';
+
+const cache = new Map();
+
+function checkAvailability(name) {
+ const takenNames = ['admin', 'root', 'superuser'];
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const result = takenNames.includes(name) ? 'Name taken' : null;
+ cache.set(name, result);
+ resolve(result);
+ }, 500);
+ });
+}
+
+export default function UnstyledFieldAsync() {
+ const [loading, setLoading] = React.useState(false);
+
+ async function handleValidate(value) {
+ const name = value;
+
+ if (name === '') {
+ return null;
+ }
+
+ const isCached = cache.has(name);
+ if (isCached) {
+ return cache.get(name);
+ }
+
+ setLoading(true);
+
+ try {
+ const error = await checkAvailability(name);
+ setLoading(false);
+ return error;
+ } catch (e) {
+ setLoading(false);
+ return 'Failed to fetch name availability';
+ }
+ }
+
+ return (
+
+
Handle availability checker
+
+
+ @
+
+ {(state) => (
+
+ )}
+
+
+
+ {(state) => {
+ if (loading) {
+ return Checking availability... ;
+ }
+
+ if (state.value === '') {
+ return Enter a name ;
+ }
+
+ if (!state.validity.customError) {
+ return (
+
+ @{state.value} is available
+
+ );
+ }
+
+ return ;
+ }}
+
+
+
+ );
+}
+
+const FieldRoot = styled(Field.Root)`
+ width: 275px;
+`;
+
+const FieldControl = styled(Field.Control)`
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ width: 100%;
+ padding: 6px;
+ font-size: 100%;
+
+ &[data-field='invalid']:not([data-pending]) {
+ border-color: red;
+ background-color: rgb(255 0 0 / 0.1);
+ }
+
+ &[data-field='valid']:not([data-pending]) {
+ border-color: green;
+ background-color: rgb(0 255 0 / 0.1);
+ }
+
+ &:focus {
+ outline: 0;
+ border-color: #0078d4;
+ box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3);
+
+ &[data-field='invalid']:not([data-pending]) {
+ border-color: red;
+ box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3);
+ }
+
+ &[data-field='valid']:not([data-pending]) {
+ box-shadow: 0 0 0 3px rgba(100 200 100 / 0.3);
+ }
+ }
+`;
+
+const FieldDescription = styled(Field.Description)`
+ font-size: 90%;
+ margin: 0;
+ margin-top: 4px;
+ line-height: 1.1;
+ color: grey;
+
+ &[data-type='success'] {
+ color: green;
+ }
+`;
+
+const FieldError = styled(Field.Error)`
+ display: block;
+ font-size: 90%;
+ margin: 0;
+ margin-top: 4px;
+ line-height: 1.1;
+ color: red;
+`;
diff --git a/docs/data/base/components/field/UnstyledFieldAsync.tsx b/docs/data/base/components/field/UnstyledFieldAsync.tsx
new file mode 100644
index 0000000000..83354edc18
--- /dev/null
+++ b/docs/data/base/components/field/UnstyledFieldAsync.tsx
@@ -0,0 +1,149 @@
+import * as React from 'react';
+import * as Field from '@base_ui/react/Field';
+import { styled } from '@mui/system';
+
+const cache = new Map();
+
+function checkAvailability(name: string) {
+ const takenNames = ['admin', 'root', 'superuser'];
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const result = takenNames.includes(name) ? 'Name taken' : null;
+ cache.set(name, result);
+ resolve(result);
+ }, 500);
+ });
+}
+
+export default function UnstyledFieldAsync() {
+ const [loading, setLoading] = React.useState(false);
+
+ async function handleValidate(value: unknown) {
+ const name = value as string;
+
+ if (name === '') {
+ return null;
+ }
+
+ const isCached = cache.has(name);
+ if (isCached) {
+ return cache.get(name) as string | null;
+ }
+
+ setLoading(true);
+
+ try {
+ const error = await checkAvailability(name);
+ setLoading(false);
+ return error;
+ } catch (e) {
+ setLoading(false);
+ return 'Failed to fetch name availability';
+ }
+ }
+
+ return (
+
+
Handle availability checker
+
+
+ @
+
+ {(state) => (
+
+ )}
+
+
+
+ {(state) => {
+ if (loading) {
+ return Checking availability... ;
+ }
+
+ if (state.value === '') {
+ return Enter a name ;
+ }
+
+ if (!state.validity.customError) {
+ return (
+
+ @{state.value as string} is available
+
+ );
+ }
+
+ return ;
+ }}
+
+
+
+ );
+}
+
+const FieldRoot = styled(Field.Root)`
+ width: 275px;
+`;
+
+const FieldControl = styled(Field.Control)`
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ width: 100%;
+ padding: 6px;
+ font-size: 100%;
+
+ &[data-field='invalid']:not([data-pending]) {
+ border-color: red;
+ background-color: rgb(255 0 0 / 0.1);
+ }
+
+ &[data-field='valid']:not([data-pending]) {
+ border-color: green;
+ background-color: rgb(0 255 0 / 0.1);
+ }
+
+ &:focus {
+ outline: 0;
+ border-color: #0078d4;
+ box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3);
+
+ &[data-field='invalid']:not([data-pending]) {
+ border-color: red;
+ box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3);
+ }
+
+ &[data-field='valid']:not([data-pending]) {
+ box-shadow: 0 0 0 3px rgba(100 200 100 / 0.3);
+ }
+ }
+`;
+
+const FieldDescription = styled(Field.Description)`
+ font-size: 90%;
+ margin: 0;
+ margin-top: 4px;
+ line-height: 1.1;
+ color: grey;
+
+ &[data-type='success'] {
+ color: green;
+ }
+`;
+
+const FieldError = styled(Field.Error)`
+ display: block;
+ font-size: 90%;
+ margin: 0;
+ margin-top: 4px;
+ line-height: 1.1;
+ color: red;
+`;
diff --git a/docs/data/base/components/field/UnstyledFieldIntroduction/system/index.js b/docs/data/base/components/field/UnstyledFieldIntroduction/system/index.js
new file mode 100644
index 0000000000..4405f78b64
--- /dev/null
+++ b/docs/data/base/components/field/UnstyledFieldIntroduction/system/index.js
@@ -0,0 +1,86 @@
+import * as React from 'react';
+import * as Field from '@base_ui/react/Field';
+import { styled } from '@mui/system';
+
+export default function UnstyledFieldIntroduction() {
+ return (
+ (value === 'admin' ? 'Name not allowed' : null)}>
+
+ Name
+
+
+
+ {({ validity, value }) => {
+ if (
+ validity.valueMissing ||
+ validity.patternMismatch ||
+ value === 'admin'
+ ) {
+ return null;
+ }
+
+ return (
+
+ Your name will be visible on your profile.
+
+ );
+ }}
+
+
+
+
+ Only alphanumeric characters are allowed (a-z, A-Z, 0-9).
+
+
+ );
+}
+
+const FieldRoot = styled(Field.Root)`
+ width: 275px;
+`;
+
+const FieldControl = styled(Field.Control)`
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ width: 100%;
+ padding: 6px;
+ font-size: 100%;
+
+ &[data-field='invalid'] {
+ border-color: red;
+ background-color: rgb(255 0 0 / 0.1);
+ }
+
+ &:focus {
+ outline: 0;
+ border-color: #0078d4;
+ box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3);
+
+ &[data-field='invalid'] {
+ border-color: red;
+ box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3);
+ }
+ }
+`;
+
+const FieldDescription = styled(Field.Description)`
+ font-size: 90%;
+ margin-bottom: 0;
+ margin-top: 4px;
+ line-height: 1.1;
+ color: grey;
+
+ &[data-error] {
+ color: red;
+ }
+`;
+
+const FieldError = styled(Field.Error)`
+ display: block;
+ font-size: 90%;
+ margin: 0;
+ margin-bottom: 0;
+ margin-top: 4px;
+ line-height: 1.1;
+ color: red;
+`;
diff --git a/docs/data/base/components/field/UnstyledFieldIntroduction/system/index.tsx b/docs/data/base/components/field/UnstyledFieldIntroduction/system/index.tsx
new file mode 100644
index 0000000000..4405f78b64
--- /dev/null
+++ b/docs/data/base/components/field/UnstyledFieldIntroduction/system/index.tsx
@@ -0,0 +1,86 @@
+import * as React from 'react';
+import * as Field from '@base_ui/react/Field';
+import { styled } from '@mui/system';
+
+export default function UnstyledFieldIntroduction() {
+ return (
+ (value === 'admin' ? 'Name not allowed' : null)}>
+
+ Name
+
+
+
+ {({ validity, value }) => {
+ if (
+ validity.valueMissing ||
+ validity.patternMismatch ||
+ value === 'admin'
+ ) {
+ return null;
+ }
+
+ return (
+
+ Your name will be visible on your profile.
+
+ );
+ }}
+
+
+
+
+ Only alphanumeric characters are allowed (a-z, A-Z, 0-9).
+
+
+ );
+}
+
+const FieldRoot = styled(Field.Root)`
+ width: 275px;
+`;
+
+const FieldControl = styled(Field.Control)`
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ width: 100%;
+ padding: 6px;
+ font-size: 100%;
+
+ &[data-field='invalid'] {
+ border-color: red;
+ background-color: rgb(255 0 0 / 0.1);
+ }
+
+ &:focus {
+ outline: 0;
+ border-color: #0078d4;
+ box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3);
+
+ &[data-field='invalid'] {
+ border-color: red;
+ box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3);
+ }
+ }
+`;
+
+const FieldDescription = styled(Field.Description)`
+ font-size: 90%;
+ margin-bottom: 0;
+ margin-top: 4px;
+ line-height: 1.1;
+ color: grey;
+
+ &[data-error] {
+ color: red;
+ }
+`;
+
+const FieldError = styled(Field.Error)`
+ display: block;
+ font-size: 90%;
+ margin: 0;
+ margin-bottom: 0;
+ margin-top: 4px;
+ line-height: 1.1;
+ color: red;
+`;
diff --git a/docs/data/base/components/field/UnstyledFieldPassword.js b/docs/data/base/components/field/UnstyledFieldPassword.js
new file mode 100644
index 0000000000..eaa46599e1
--- /dev/null
+++ b/docs/data/base/components/field/UnstyledFieldPassword.js
@@ -0,0 +1,100 @@
+import * as React from 'react';
+import * as Field from '@base_ui/react/Field';
+import { styled } from '@mui/system';
+
+function validate(value) {
+ const password = value;
+ const errors = [];
+
+ if (password.length < 8) {
+ errors.push('Password must be at least 8 characters long.');
+ }
+
+ if ((password.match(/[A-Z]/g) ?? []).length < 2) {
+ errors.push('Password must contain at least 2 uppercase letters.');
+ }
+
+ if ((password.match(/[!@#$%^&*]/g) ?? []).length < 2) {
+ errors.push(
+ 'Password must contain at least 2 unique symbols from the set [!@#$%^&*].',
+ );
+ }
+
+ return errors;
+}
+
+export default function UnstyledFieldPassword() {
+ const [value, setValue] = React.useState('');
+ const errors = validate(value);
+
+ return (
+ 0}>
+ Password
+ setValue(event.currentTarget.value)}
+ />
+
+
+ {errors.map((error) => (
+ {error}
+ ))}
+
+
+
+ );
+}
+
+const FieldRoot = styled(Field.Root)`
+ width: 275px;
+`;
+
+const FieldControl = styled(Field.Control)`
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ width: 100%;
+ padding: 6px;
+ font-size: 100%;
+
+ &[data-field='valid'][data-dirty] {
+ border-color: green;
+ background-color: rgb(0 255 0 / 0.1);
+ }
+
+ &[data-field='invalid'][data-touched][data-dirty] {
+ border-color: red;
+ background-color: rgb(255 0 0 / 0.1);
+ }
+
+ &:focus {
+ outline: 0;
+ border-color: #0078d4;
+ box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3);
+
+ &[data-field='valid'][data-dirty] {
+ border-color: green;
+ box-shadow: 0 0 0 3px rgba(100 200 100 / 0.3);
+ }
+
+ &[data-field='invalid'][data-touched][data-dirty] {
+ border-color: red;
+ box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3);
+ }
+ }
+`;
+
+const FieldError = styled(Field.Error)`
+ display: block;
+ font-size: 90%;
+ margin-top: 10px;
+ line-height: 1.1;
+
+ &[data-touched][data-dirty] {
+ color: red;
+ }
+
+ ul {
+ padding: 0;
+ }
+`;
diff --git a/docs/data/base/components/field/UnstyledFieldPassword.tsx b/docs/data/base/components/field/UnstyledFieldPassword.tsx
new file mode 100644
index 0000000000..667bec5780
--- /dev/null
+++ b/docs/data/base/components/field/UnstyledFieldPassword.tsx
@@ -0,0 +1,100 @@
+import * as React from 'react';
+import * as Field from '@base_ui/react/Field';
+import { styled } from '@mui/system';
+
+function validate(value: string) {
+ const password = value;
+ const errors: string[] = [];
+
+ if (password.length < 8) {
+ errors.push('Password must be at least 8 characters long.');
+ }
+
+ if ((password.match(/[A-Z]/g) ?? []).length < 2) {
+ errors.push('Password must contain at least 2 uppercase letters.');
+ }
+
+ if ((password.match(/[!@#$%^&*]/g) ?? []).length < 2) {
+ errors.push(
+ 'Password must contain at least 2 unique symbols from the set [!@#$%^&*].',
+ );
+ }
+
+ return errors;
+}
+
+export default function UnstyledFieldPassword() {
+ const [value, setValue] = React.useState('');
+ const errors = validate(value);
+
+ return (
+ 0}>
+ Password
+ setValue(event.currentTarget.value)}
+ />
+
+
+ {errors.map((error) => (
+ {error}
+ ))}
+
+
+
+ );
+}
+
+const FieldRoot = styled(Field.Root)`
+ width: 275px;
+`;
+
+const FieldControl = styled(Field.Control)`
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ width: 100%;
+ padding: 6px;
+ font-size: 100%;
+
+ &[data-field='valid'][data-dirty] {
+ border-color: green;
+ background-color: rgb(0 255 0 / 0.1);
+ }
+
+ &[data-field='invalid'][data-touched][data-dirty] {
+ border-color: red;
+ background-color: rgb(255 0 0 / 0.1);
+ }
+
+ &:focus {
+ outline: 0;
+ border-color: #0078d4;
+ box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3);
+
+ &[data-field='valid'][data-dirty] {
+ border-color: green;
+ box-shadow: 0 0 0 3px rgba(100 200 100 / 0.3);
+ }
+
+ &[data-field='invalid'][data-touched][data-dirty] {
+ border-color: red;
+ box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3);
+ }
+ }
+`;
+
+const FieldError = styled(Field.Error)`
+ display: block;
+ font-size: 90%;
+ margin-top: 10px;
+ line-height: 1.1;
+
+ &[data-touched][data-dirty] {
+ color: red;
+ }
+
+ ul {
+ padding: 0;
+ }
+`;
diff --git a/docs/data/base/components/field/UnstyledFieldPassword.tsx.preview b/docs/data/base/components/field/UnstyledFieldPassword.tsx.preview
new file mode 100644
index 0000000000..6dcd33da03
--- /dev/null
+++ b/docs/data/base/components/field/UnstyledFieldPassword.tsx.preview
@@ -0,0 +1,15 @@
+ 0}>
+ Password
+ setValue(event.currentTarget.value)}
+ />
+
+
+ {errors.map((error) => (
+ {error}
+ ))}
+
+
+
\ No newline at end of file
diff --git a/docs/data/base/components/field/UnstyledFieldServerError.js b/docs/data/base/components/field/UnstyledFieldServerError.js
new file mode 100644
index 0000000000..1f60d6e6ba
--- /dev/null
+++ b/docs/data/base/components/field/UnstyledFieldServerError.js
@@ -0,0 +1,161 @@
+import * as React from 'react';
+import * as Field from '@base_ui/react/Field';
+import { styled } from '@mui/system';
+
+export default function UnstyledFieldServerError() {
+ const [error, setError] = React.useState(false);
+ const [status, setStatus] = React.useState('initial');
+
+ const controlRef = React.useRef(null);
+
+ async function handleSubmit(event) {
+ event.preventDefault();
+
+ if (error || !controlRef.current?.validity.valid) {
+ return;
+ }
+
+ const formData = new FormData(event.currentTarget);
+ const email = formData.get('email');
+
+ setStatus('loading');
+
+ // Mimic a server request
+ await new Promise((resolve) => {
+ setTimeout(resolve, 1000);
+ });
+
+ if (email && email.endsWith('@example.com')) {
+ setStatus('error');
+ setError(true);
+ } else {
+ setStatus('success');
+ }
+
+ controlRef.current?.focus();
+ }
+
+ return (
+
+ );
+}
+
+const FieldRoot = styled(Field.Root)`
+ width: 275px;
+`;
+
+const FieldControl = styled(Field.Control)`
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ width: 100%;
+ padding: 6px;
+ font-size: 100%;
+
+ &[data-field='invalid'] {
+ border-color: red;
+ background-color: rgb(255 0 0 / 0.1);
+ }
+
+ &:focus {
+ outline: 0;
+ border-color: #0078d4;
+ box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3);
+
+ &[data-field='invalid'] {
+ border-color: red;
+ box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3);
+ }
+ }
+`;
+
+const FieldError = styled(Field.Error)`
+ display: block;
+ font-size: 90%;
+ margin: 0;
+ padding: 0;
+ margin-top: 10px;
+ line-height: 1.1;
+
+ &[data-dirty],
+ &[data-touched] {
+ color: red;
+ }
+`;
+
+const FieldSuccess = styled(Field.Description)`
+ font-size: 90%;
+ margin: 0;
+ padding: 0;
+ margin-top: 10px;
+ color: green;
+`;
+
+const FieldDescription = styled('p')`
+ font-size: 90%;
+ margin: 0;
+ padding: 0;
+ margin-top: 10px;
+ line-height: 1.1;
+ color: grey;
+`;
+
+const FieldSubmit = styled('button')`
+ display: block;
+ margin-top: 10px;
+ padding: 10px;
+ width: 100%;
+ font-size: 100%;
+ background-color: #0078d4;
+ color: white;
+ border: none;
+ border-radius: 4px;
+
+ &[aria-disabled='true'] {
+ background-color: #ddd;
+ color: black;
+ }
+`;
diff --git a/docs/data/base/components/field/UnstyledFieldServerError.tsx b/docs/data/base/components/field/UnstyledFieldServerError.tsx
new file mode 100644
index 0000000000..7dfb843aaa
--- /dev/null
+++ b/docs/data/base/components/field/UnstyledFieldServerError.tsx
@@ -0,0 +1,162 @@
+import * as React from 'react';
+import * as Field from '@base_ui/react/Field';
+import { styled } from '@mui/system';
+
+type Status = 'initial' | 'loading' | 'success' | 'error';
+
+export default function UnstyledFieldServerError() {
+ const [error, setError] = React.useState(false);
+ const [status, setStatus] = React.useState('initial');
+
+ const controlRef = React.useRef(null);
+
+ async function handleSubmit(event: React.FormEvent) {
+ event.preventDefault();
+
+ if (error || !controlRef.current?.validity.valid) {
+ return;
+ }
+
+ const formData = new FormData(event.currentTarget);
+ const email = formData.get('email') as string;
+
+ setStatus('loading');
+
+ // Mimic a server request
+ await new Promise((resolve) => {
+ setTimeout(resolve, 1000);
+ });
+
+ if (email && email.endsWith('@example.com')) {
+ setStatus('error');
+ setError(true);
+ } else {
+ setStatus('success');
+ }
+
+ controlRef.current?.focus();
+ }
+
+ return (
+
+ );
+}
+
+const FieldRoot = styled(Field.Root)`
+ width: 275px;
+`;
+
+const FieldControl = styled(Field.Control)`
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ width: 100%;
+ padding: 6px;
+ font-size: 100%;
+
+ &[data-field='invalid'] {
+ border-color: red;
+ background-color: rgb(255 0 0 / 0.1);
+ }
+
+ &:focus {
+ outline: 0;
+ border-color: #0078d4;
+ box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3);
+
+ &[data-field='invalid'] {
+ border-color: red;
+ box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3);
+ }
+ }
+`;
+
+const FieldError = styled(Field.Error)`
+ display: block;
+ font-size: 90%;
+ margin: 0;
+ padding: 0;
+ margin-top: 10px;
+ line-height: 1.1;
+
+ &[data-dirty],
+ &[data-touched] {
+ color: red;
+ }
+`;
+
+const FieldSuccess = styled(Field.Description)`
+ font-size: 90%;
+ margin: 0;
+ padding: 0;
+ margin-top: 10px;
+ color: green;
+`;
+
+const FieldDescription = styled('p')`
+ font-size: 90%;
+ margin: 0;
+ padding: 0;
+ margin-top: 10px;
+ line-height: 1.1;
+ color: grey;
+`;
+
+const FieldSubmit = styled('button')`
+ display: block;
+ margin-top: 10px;
+ padding: 10px;
+ width: 100%;
+ font-size: 100%;
+ background-color: #0078d4;
+ color: white;
+ border: none;
+ border-radius: 4px;
+
+ &[aria-disabled='true'] {
+ background-color: #ddd;
+ color: black;
+ }
+`;
diff --git a/docs/data/base/components/field/field.md b/docs/data/base/components/field/field.md
new file mode 100644
index 0000000000..3b6c205f1b
--- /dev/null
+++ b/docs/data/base/components/field/field.md
@@ -0,0 +1,275 @@
+---
+productId: base-ui
+title: React Field component and hook
+components: FieldRoot, FieldLabel, FieldDescription, FieldError, FieldControl, FieldValidity
+githubLabel: 'component: field'
+packageName: '@base_ui/react'
+---
+
+# Field
+
+Fields represent an individual section of a form containing an associated control and label, as well as any description or validation messages.
+
+{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}
+
+{{"component": "modules/components/ComponentPageTabs.js"}}
+
+{{"demo": "UnstyledFieldIntroduction", "defaultCodeOpen": false, "bg": "gradient"}}
+
+## Installation
+
+Base UI components are all available as a single package.
+
+
+
+```bash npm
+npm install @base_ui/react
+```
+
+```bash yarn
+yarn add @base_ui/react
+```
+
+```bash pnpm
+pnpm add @base_ui/react
+```
+
+
+
+Once you have the package installed, import the component.
+
+```ts
+import * as Field from '@base_ui/react/Field';
+```
+
+## Anatomy
+
+Fields are implemented using a collection of related components:
+
+- ` ` is a top-level component that wraps all other components.
+- ` ` renders the control when not using a native Base UI input component.
+- ` ` renders a label for the control.
+- ` ` renders an optional description for the control to provide additional information.
+- ` ` renders error messages for the control.
+- ` ` accepts a function as a child that enables reading raw `ValidityState` to render custom JSX.
+
+```jsx
+
+
+
+
+
+
+
+```
+
+## Labeling and descriptive help text
+
+All Base UI input components are aware of Base UI's `Field` component. The label and description are automatically wired to these components when placed inside a `Field.Root`:
+
+```jsx
+
+
+
+
+ My checkbox
+ My description
+
+```
+
+When using a native control like `input` or a custom component which is not aware of Base UI's `Field`, use `Field.Control`:
+
+```jsx
+
+
+ My input
+ My description
+
+```
+
+The `render` prop allows you to pass a custom component or tag, different from the default of `input`:
+
+```jsx
+ } />
+```
+
+## Validation
+
+When adding native HTML validation props like `required` or `pattern`, `Field.Error` renders error messages inside of it automatically:
+
+```jsx
+
+ My input
+
+
+
+```
+
+The `children` by default is the browser's native message, which is automatically internationalized. You may pass custom `children` instead:
+
+```jsx
+
+
+ Field is required
+
+```
+
+### Individual constraint validation failures
+
+When there are multiple HTML validation props, you can target individual validity state failures using the `show` prop to render custom messages:
+
+```jsx
+
+
+ Field is required
+
+ Only alphanumeric characters allowed
+
+
+```
+
+For the list of supported `show` strings, visit [`ValidityState` on MDN](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState#instance_properties).
+
+### Custom validation
+
+In addition to the native HTML constraint validation, custom validation can be used by specifying a `validate` function on `Field.Root`. It receives the control's `value` as its argument, and returns an error string or array of error strings if the field is invalid, or `null` otherwise.
+
+```jsx
+
+ value === 'password' ? 'Cannot literally use `password` as your password.' : null
+ }
+>
+
+ Password
+
+
+```
+
+:::info
+For Base UI input components, `value` represents the component's value type, while for native elements, it is always the native `element.value` DOM property. Attach a `ref` to the `Control` element and access it to read its state inside the `validate` function for further control as an alternative if necessary.
+:::
+
+To customize the rendering of multiple messages, you can use the `Validity` subcomponent:
+
+```jsx
+ {
+ const errors = [];
+ if (value.length < 8) {
+ errors.push('Password must be at least 8 characters long.');
+ }
+ if (value === 'password') {
+ errors.push('Cannot literally use `password` as your password.');
+ }
+ return errors;
+ }}
+>
+
+ Password
+
+
+
+ {(state) => state.errors.map((error) => {error} )}
+
+
+
+
+```
+
+The `Validity` subcomponent enables rendering custom JSX based on the `state` parameter, which contains the following properties:
+
+- `state.validity`, the field's `ValidityState`
+- `state.errors`, an array of custom errors returned from the `validate` prop (if present)
+- `state.error`, a custom error string returned from the `validate` prop (if present)
+- `state.value`, the field control's current value
+- `state.initialValue`, the field control's initial value upon mount
+
+It can be placed anywhere inside `Field.Root`, including other Field subcomponents.
+
+### Controlled validity
+
+When the `invalid` prop is applied to `Field.Root`, the Field is placed into an invalid state regardless of client-side validation. In this state, a given `Field.Error` message can be forced to be shown by specifying a `forceShow` prop.
+
+This is useful for server-side error messages, or displaying errors initially during SSR phase.
+
+```jsx
+const [serverErrors, setServerErrors] = React.useState({
+ email: false,
+});
+
+return (
+
+
+ Client-side only error message
+
+ Client + server-side error message
+
+
+ Server-side only message
+
+
+);
+```
+
+The `show` prop is for client-side validation, while the `forceShow` prop is for server-side validation. Both can be combined together to share the same error message.
+
+Performing an email validity check on the server:
+
+{{"demo": "UnstyledFieldServerError.js", "defaultCodeOpen": false}}
+
+Errors shown initially for password validation:
+
+{{"demo": "UnstyledFieldPassword.js", "defaultCodeOpen": false}}
+
+### Realtime and async validation
+
+`validateOnChange` reports the validity of the control on every `change` event, such as a keypress:
+
+```jsx
+
+```
+
+The `validate` function can also be async by returning a promise, enabling inline server-side validation through network requests.
+
+In the demo below, the taken names are `admin`, `root`, and `superuser` — every other name is available. For demonstration purposes, a fake network request that takes 500ms is initiated to mimic a trip to the server to check for availability on the back-end.
+
+{{"demo": "UnstyledFieldAsync.js", "defaultCodeOpen": false}}
+
+The `change` validation is debounced by 500ms to avoid firing a network request on every keystroke by specifying the `validateDebounceTime` prop:
+
+```jsx
+
+```
+
+## Styling
+
+The `[data-field="valid"]` and `[data-field="invalid"]` style hooks determine if the field is invalid or not:
+
+```jsx
+
+
+
+```
+
+```css
+.FieldControl[data-field='invalid'] {
+ color: red;
+}
+```
+
+`[data-touched]` is applied if the field has been "touched": blurred after being interacted with, or submitted if pressing Enter on an input.
+
+```css
+.FieldControl[data-touched] {
+ color: red;
+}
+```
+
+`[data-dirty]` is applied if the field's value has been changed from its initial one.
+
+```css
+.FieldControl[data-dirty] {
+ color: orange;
+}
+```
diff --git a/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.js b/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.js
new file mode 100644
index 0000000000..ab2c01ea7a
--- /dev/null
+++ b/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.js
@@ -0,0 +1,50 @@
+import * as React from 'react';
+import * as Field from '@base_ui/react/Field';
+import * as Fieldset from '@base_ui/react/Fieldset';
+import { styled } from '@mui/system';
+
+export default function UnstyledFieldsetIntroduction() {
+ return (
+
+ Account details
+
+ Name
+
+
+
+ Address
+
+
+
+ Bio
+ } />
+
+
+ );
+}
+
+const FieldsetRoot = styled(Fieldset.Root)`
+ border: none;
+ width: 300px;
+`;
+
+const FieldsetLegend = styled(Fieldset.Legend)`
+ display: inline-block;
+ font-size: 125%;
+ font-weight: bold;
+ margin-bottom: 10px;
+`;
+
+const FieldControl = styled(Field.Control)`
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ width: 100%;
+ padding: 6px;
+ margin-bottom: 5px;
+
+ &[data-textarea] {
+ min-width: 300px;
+ max-width: 300px;
+ min-height: 100px;
+ }
+`;
diff --git a/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.tsx b/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.tsx
new file mode 100644
index 0000000000..ab2c01ea7a
--- /dev/null
+++ b/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.tsx
@@ -0,0 +1,50 @@
+import * as React from 'react';
+import * as Field from '@base_ui/react/Field';
+import * as Fieldset from '@base_ui/react/Fieldset';
+import { styled } from '@mui/system';
+
+export default function UnstyledFieldsetIntroduction() {
+ return (
+
+ Account details
+
+ Name
+
+
+
+ Address
+
+
+
+ Bio
+ } />
+
+
+ );
+}
+
+const FieldsetRoot = styled(Fieldset.Root)`
+ border: none;
+ width: 300px;
+`;
+
+const FieldsetLegend = styled(Fieldset.Legend)`
+ display: inline-block;
+ font-size: 125%;
+ font-weight: bold;
+ margin-bottom: 10px;
+`;
+
+const FieldControl = styled(Field.Control)`
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ width: 100%;
+ padding: 6px;
+ margin-bottom: 5px;
+
+ &[data-textarea] {
+ min-width: 300px;
+ max-width: 300px;
+ min-height: 100px;
+ }
+`;
diff --git a/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.tsx.preview b/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.tsx.preview
new file mode 100644
index 0000000000..7bdd82c548
--- /dev/null
+++ b/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.tsx.preview
@@ -0,0 +1,15 @@
+
+ Account details
+
+ Name
+
+
+
+ Address
+
+
+
+ Bio
+ } />
+
+
\ No newline at end of file
diff --git a/docs/data/base/components/fieldset/fieldset.md b/docs/data/base/components/fieldset/fieldset.md
new file mode 100644
index 0000000000..eb26449596
--- /dev/null
+++ b/docs/data/base/components/fieldset/fieldset.md
@@ -0,0 +1,78 @@
+---
+productId: base-ui
+title: React Fieldset component and hook
+components: FieldsetRoot, FieldsetLegend
+githubLabel: 'component: fieldset'
+packageName: '@base_ui/react'
+---
+
+# Fieldset
+
+Fieldsets group multiple fields together with a label. This is a light wrapper over the native `fieldset` and `legend` elements, but makes the `legend` element easily stylable.
+
+{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}
+
+{{"component": "modules/components/ComponentPageTabs.js"}}
+
+{{"demo": "UnstyledFieldsetIntroduction", "defaultCodeOpen": false, "bg": "gradient"}}
+
+## Installation
+
+Base UI components are all available as a single package.
+
+
+
+```bash npm
+npm install @base_ui/react
+```
+
+```bash yarn
+yarn add @base_ui/react
+```
+
+```bash pnpm
+pnpm add @base_ui/react
+```
+
+
+
+Once you have the package installed, import the component.
+
+```ts
+import * as Fieldset from '@base_ui/react/Fieldset';
+```
+
+## Anatomy
+
+Fieldsets are composed of two components:
+
+- ` ` renders the `fieldset` element.
+- ` ` renders a label for the fieldset.
+
+```jsx
+
+
+
+```
+
+## Usage with Fields
+
+`Field` components are placed inside the `Fieldset` component.
+
+```jsx
+
+ Account details
+
+ Name
+
+
+
+ Address
+
+
+
+ Bio
+ } />
+
+
+```
diff --git a/docs/data/base/components/focus-trap/BasicFocusTrap.js b/docs/data/base/components/focus-trap/BasicFocusTrap.js
index cc908a1d5f..f8a10ed484 100644
--- a/docs/data/base/components/focus-trap/BasicFocusTrap.js
+++ b/docs/data/base/components/focus-trap/BasicFocusTrap.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import Box from '@mui/system/Box';
-import { FocusTrap } from '@base_ui/react/FocusTrap';
+import { FocusTrap } from '@base_ui/react/legacy/FocusTrap';
export default function BasicFocusTrap() {
const [open, setOpen] = React.useState(false);
diff --git a/docs/data/base/components/focus-trap/BasicFocusTrap.tsx b/docs/data/base/components/focus-trap/BasicFocusTrap.tsx
index cc908a1d5f..f8a10ed484 100644
--- a/docs/data/base/components/focus-trap/BasicFocusTrap.tsx
+++ b/docs/data/base/components/focus-trap/BasicFocusTrap.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import Box from '@mui/system/Box';
-import { FocusTrap } from '@base_ui/react/FocusTrap';
+import { FocusTrap } from '@base_ui/react/legacy/FocusTrap';
export default function BasicFocusTrap() {
const [open, setOpen] = React.useState(false);
diff --git a/docs/data/base/components/focus-trap/ContainedToggleTrappedFocus.js b/docs/data/base/components/focus-trap/ContainedToggleTrappedFocus.js
index f7d875e6d9..1090e56fcc 100644
--- a/docs/data/base/components/focus-trap/ContainedToggleTrappedFocus.js
+++ b/docs/data/base/components/focus-trap/ContainedToggleTrappedFocus.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import Stack from '@mui/system/Stack';
-import { FocusTrap } from '@base_ui/react/FocusTrap';
+import { FocusTrap } from '@base_ui/react/legacy/FocusTrap';
export default function ContainedToggleTrappedFocus() {
const [open, setOpen] = React.useState(false);
diff --git a/docs/data/base/components/focus-trap/ContainedToggleTrappedFocus.tsx b/docs/data/base/components/focus-trap/ContainedToggleTrappedFocus.tsx
index f7d875e6d9..1090e56fcc 100644
--- a/docs/data/base/components/focus-trap/ContainedToggleTrappedFocus.tsx
+++ b/docs/data/base/components/focus-trap/ContainedToggleTrappedFocus.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import Stack from '@mui/system/Stack';
-import { FocusTrap } from '@base_ui/react/FocusTrap';
+import { FocusTrap } from '@base_ui/react/legacy/FocusTrap';
export default function ContainedToggleTrappedFocus() {
const [open, setOpen] = React.useState(false);
diff --git a/docs/data/base/components/focus-trap/DisableEnforceFocus.js b/docs/data/base/components/focus-trap/DisableEnforceFocus.js
index 4a5aec32fc..116542d7e6 100644
--- a/docs/data/base/components/focus-trap/DisableEnforceFocus.js
+++ b/docs/data/base/components/focus-trap/DisableEnforceFocus.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import Box from '@mui/system/Box';
-import { FocusTrap } from '@base_ui/react/FocusTrap';
+import { FocusTrap } from '@base_ui/react/legacy/FocusTrap';
export default function DisableEnforceFocus() {
const [open, setOpen] = React.useState(false);
diff --git a/docs/data/base/components/focus-trap/DisableEnforceFocus.tsx b/docs/data/base/components/focus-trap/DisableEnforceFocus.tsx
index 4a5aec32fc..116542d7e6 100644
--- a/docs/data/base/components/focus-trap/DisableEnforceFocus.tsx
+++ b/docs/data/base/components/focus-trap/DisableEnforceFocus.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import Box from '@mui/system/Box';
-import { FocusTrap } from '@base_ui/react/FocusTrap';
+import { FocusTrap } from '@base_ui/react/legacy/FocusTrap';
export default function DisableEnforceFocus() {
const [open, setOpen] = React.useState(false);
diff --git a/docs/data/base/components/focus-trap/LazyFocusTrap.js b/docs/data/base/components/focus-trap/LazyFocusTrap.js
index d15ad50e06..a50485968c 100644
--- a/docs/data/base/components/focus-trap/LazyFocusTrap.js
+++ b/docs/data/base/components/focus-trap/LazyFocusTrap.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import Box from '@mui/system/Box';
-import { FocusTrap } from '@base_ui/react/FocusTrap';
+import { FocusTrap } from '@base_ui/react/legacy/FocusTrap';
export default function LazyFocusTrap() {
const [open, setOpen] = React.useState(false);
diff --git a/docs/data/base/components/focus-trap/LazyFocusTrap.tsx b/docs/data/base/components/focus-trap/LazyFocusTrap.tsx
index d15ad50e06..a50485968c 100644
--- a/docs/data/base/components/focus-trap/LazyFocusTrap.tsx
+++ b/docs/data/base/components/focus-trap/LazyFocusTrap.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import Box from '@mui/system/Box';
-import { FocusTrap } from '@base_ui/react/FocusTrap';
+import { FocusTrap } from '@base_ui/react/legacy/FocusTrap';
export default function LazyFocusTrap() {
const [open, setOpen] = React.useState(false);
diff --git a/docs/data/base/components/focus-trap/PortalFocusTrap.js b/docs/data/base/components/focus-trap/PortalFocusTrap.js
index 71ec041606..95bb695353 100644
--- a/docs/data/base/components/focus-trap/PortalFocusTrap.js
+++ b/docs/data/base/components/focus-trap/PortalFocusTrap.js
@@ -1,7 +1,7 @@
import * as React from 'react';
import Box from '@mui/system/Box';
-import { Portal } from '@base_ui/react/Portal';
-import { FocusTrap } from '@base_ui/react/FocusTrap';
+import { Portal } from '@base_ui/react/legacy/Portal';
+import { FocusTrap } from '@base_ui/react/legacy/FocusTrap';
export default function PortalFocusTrap() {
const [open, setOpen] = React.useState(false);
diff --git a/docs/data/base/components/focus-trap/PortalFocusTrap.tsx b/docs/data/base/components/focus-trap/PortalFocusTrap.tsx
index 9c070e56be..1f8127fa6f 100644
--- a/docs/data/base/components/focus-trap/PortalFocusTrap.tsx
+++ b/docs/data/base/components/focus-trap/PortalFocusTrap.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import Box from '@mui/system/Box';
-import { Portal } from '@base_ui/react/Portal';
-import { FocusTrap } from '@base_ui/react/FocusTrap';
+import { Portal } from '@base_ui/react/legacy/Portal';
+import { FocusTrap } from '@base_ui/react/legacy/FocusTrap';
export default function PortalFocusTrap() {
const [open, setOpen] = React.useState(false);
diff --git a/docs/data/base/components/focus-trap/focus-trap.md b/docs/data/base/components/focus-trap/focus-trap.md
index 587d1744d6..788cb357e9 100644
--- a/docs/data/base/components/focus-trap/focus-trap.md
+++ b/docs/data/base/components/focus-trap/focus-trap.md
@@ -20,7 +20,7 @@ Focus Trap is a utility component that's useful when implementing an overlay suc
## Component
```jsx
-import { FocusTrap } from '@base_ui/react/FocusTrap';
+import { FocusTrap } from '@base_ui/react/legacy/FocusTrap';
```
Focus Trap wraps around the UI elements that should hold the user's focus.
diff --git a/docs/data/base/components/form-control/BasicFormControl/css/index.js b/docs/data/base/components/form-control/BasicFormControl/css/index.js
deleted file mode 100644
index 5742eafdf0..0000000000
--- a/docs/data/base/components/form-control/BasicFormControl/css/index.js
+++ /dev/null
@@ -1,144 +0,0 @@
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import { FormControl, useFormControlContext } from '@base_ui/react/FormControl';
-import { Input, inputClasses } from '@base_ui/react/Input';
-import { useTheme } from '@mui/system';
-import clsx from 'clsx';
-
-export default function BasicFormControl() {
- return (
-
-
- Name
-
-
-
-
-
- );
-}
-
-const Label = React.forwardRef(({ className: classNameProp, children }, ref) => {
- const formControlContext = useFormControlContext();
- const [dirty, setDirty] = React.useState(false);
-
- React.useEffect(() => {
- if (formControlContext?.filled) {
- setDirty(true);
- }
- }, [formControlContext]);
-
- if (formControlContext === undefined) {
- return {children}
;
- }
-
- const { error, required, filled } = formControlContext;
- const showRequiredError = dirty && required && !filled;
-
- return (
-
- {children}
- {required ? ' *' : ''}
-
- );
-});
-
-const HelperText = React.forwardRef((props, ref) => {
- const { className, ...other } = props;
- const formControlContext = useFormControlContext();
- const [dirty, setDirty] = React.useState(false);
-
- React.useEffect(() => {
- if (formControlContext?.filled) {
- setDirty(true);
- }
- }, [formControlContext]);
-
- if (formControlContext === undefined) {
- return null;
- }
-
- const { required, filled } = formControlContext;
- const showRequiredError = dirty && required && !filled;
-
- return showRequiredError ? (
-
- This field is required.
-
- ) : null;
-});
-
-HelperText.propTypes = {
- className: PropTypes.string,
-};
-
-const cyan = {
- 50: '#E9F8FC',
- 100: '#BDEBF4',
- 200: '#99D8E5',
- 300: '#66BACC',
- 400: '#1F94AD',
- 500: '#0D5463',
- 600: '#094855',
- 700: '#063C47',
- 800: '#043039',
- 900: '#022127',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-function Styles() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
- );
-}
diff --git a/docs/data/base/components/form-control/BasicFormControl/css/index.tsx b/docs/data/base/components/form-control/BasicFormControl/css/index.tsx
deleted file mode 100644
index b03c4f1d25..0000000000
--- a/docs/data/base/components/form-control/BasicFormControl/css/index.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import * as React from 'react';
-import { FormControl, useFormControlContext } from '@base_ui/react/FormControl';
-import { Input, inputClasses } from '@base_ui/react/Input';
-import { useTheme } from '@mui/system';
-import clsx from 'clsx';
-
-export default function BasicFormControl() {
- return (
-
-
- Name
-
-
-
-
-
- );
-}
-
-const Label = React.forwardRef<
- HTMLParagraphElement,
- { className?: string; children?: React.ReactNode }
->(({ className: classNameProp, children }, ref) => {
- const formControlContext = useFormControlContext();
- const [dirty, setDirty] = React.useState(false);
-
- React.useEffect(() => {
- if (formControlContext?.filled) {
- setDirty(true);
- }
- }, [formControlContext]);
-
- if (formControlContext === undefined) {
- return {children}
;
- }
-
- const { error, required, filled } = formControlContext;
- const showRequiredError = dirty && required && !filled;
-
- return (
-
- {children}
- {required ? ' *' : ''}
-
- );
-});
-
-const HelperText = React.forwardRef(
- (props, ref) => {
- const { className, ...other } = props;
- const formControlContext = useFormControlContext();
- const [dirty, setDirty] = React.useState(false);
-
- React.useEffect(() => {
- if (formControlContext?.filled) {
- setDirty(true);
- }
- }, [formControlContext]);
-
- if (formControlContext === undefined) {
- return null;
- }
-
- const { required, filled } = formControlContext;
- const showRequiredError = dirty && required && !filled;
-
- return showRequiredError ? (
-
- This field is required.
-
- ) : null;
- },
-);
-
-const cyan = {
- 50: '#E9F8FC',
- 100: '#BDEBF4',
- 200: '#99D8E5',
- 300: '#66BACC',
- 400: '#1F94AD',
- 500: '#0D5463',
- 600: '#094855',
- 700: '#063C47',
- 800: '#043039',
- 900: '#022127',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-function Styles() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
- );
-}
diff --git a/docs/data/base/components/form-control/BasicFormControl/css/index.tsx.preview b/docs/data/base/components/form-control/BasicFormControl/css/index.tsx.preview
deleted file mode 100644
index a09c70ab02..0000000000
--- a/docs/data/base/components/form-control/BasicFormControl/css/index.tsx.preview
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- Name
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/data/base/components/form-control/BasicFormControl/system/index.js b/docs/data/base/components/form-control/BasicFormControl/system/index.js
deleted file mode 100644
index 5d879458d6..0000000000
--- a/docs/data/base/components/form-control/BasicFormControl/system/index.js
+++ /dev/null
@@ -1,122 +0,0 @@
-import * as React from 'react';
-import { FormControl, useFormControlContext } from '@base_ui/react/FormControl';
-import { Input, inputClasses } from '@base_ui/react/Input';
-import { styled } from '@mui/system';
-import clsx from 'clsx';
-
-export default function BasicFormControl() {
- return (
-
- Name
-
-
-
- );
-}
-
-const StyledInput = styled(Input)(
- ({ theme }) => `
-
- .${inputClasses.input} {
- width: 320px;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- font-weight: 400;
- line-height: 1.5;
- padding: 8px 12px;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 2px ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus {
- outline: 0;
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
- }
- }
-`,
-);
-
-const Label = styled(({ children, className }) => {
- const formControlContext = useFormControlContext();
- const [dirty, setDirty] = React.useState(false);
-
- React.useEffect(() => {
- if (formControlContext?.filled) {
- setDirty(true);
- }
- }, [formControlContext]);
-
- if (formControlContext === undefined) {
- return {children}
;
- }
-
- const { error, required, filled } = formControlContext;
- const showRequiredError = dirty && required && !filled;
-
- return (
-
- {children}
- {required ? ' *' : ''}
-
- );
-})`
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- margin-bottom: 4px;
-
- &.invalid {
- color: red;
- }
-`;
-
-const HelperText = styled((props) => {
- const formControlContext = useFormControlContext();
- const [dirty, setDirty] = React.useState(false);
-
- React.useEffect(() => {
- if (formControlContext?.filled) {
- setDirty(true);
- }
- }, [formControlContext]);
-
- if (formControlContext === undefined) {
- return null;
- }
-
- const { required, filled } = formControlContext;
- const showRequiredError = dirty && required && !filled;
-
- return showRequiredError ? This field is required.
: null;
-})`
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
-`;
-
-const blue = {
- 100: '#DAECFF',
- 200: '#b6daff',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 900: '#003A75',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
diff --git a/docs/data/base/components/form-control/BasicFormControl/system/index.tsx b/docs/data/base/components/form-control/BasicFormControl/system/index.tsx
deleted file mode 100644
index 546863e267..0000000000
--- a/docs/data/base/components/form-control/BasicFormControl/system/index.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import * as React from 'react';
-import { FormControl, useFormControlContext } from '@base_ui/react/FormControl';
-import { Input, inputClasses } from '@base_ui/react/Input';
-import { styled } from '@mui/system';
-import clsx from 'clsx';
-
-export default function BasicFormControl() {
- return (
-
- Name
-
-
-
- );
-}
-
-const StyledInput = styled(Input)(
- ({ theme }) => `
-
- .${inputClasses.input} {
- width: 320px;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- font-weight: 400;
- line-height: 1.5;
- padding: 8px 12px;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 2px ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus {
- outline: 0;
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
- }
- }
-`,
-);
-
-const Label = styled(
- ({ children, className }: { children?: React.ReactNode; className?: string }) => {
- const formControlContext = useFormControlContext();
- const [dirty, setDirty] = React.useState(false);
-
- React.useEffect(() => {
- if (formControlContext?.filled) {
- setDirty(true);
- }
- }, [formControlContext]);
-
- if (formControlContext === undefined) {
- return {children}
;
- }
-
- const { error, required, filled } = formControlContext;
- const showRequiredError = dirty && required && !filled;
-
- return (
-
- {children}
- {required ? ' *' : ''}
-
- );
- },
-)`
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- margin-bottom: 4px;
-
- &.invalid {
- color: red;
- }
-`;
-
-const HelperText = styled((props: {}) => {
- const formControlContext = useFormControlContext();
- const [dirty, setDirty] = React.useState(false);
-
- React.useEffect(() => {
- if (formControlContext?.filled) {
- setDirty(true);
- }
- }, [formControlContext]);
-
- if (formControlContext === undefined) {
- return null;
- }
-
- const { required, filled } = formControlContext;
- const showRequiredError = dirty && required && !filled;
-
- return showRequiredError ? This field is required.
: null;
-})`
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
-`;
-
-const blue = {
- 100: '#DAECFF',
- 200: '#b6daff',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 900: '#003A75',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
diff --git a/docs/data/base/components/form-control/BasicFormControl/system/index.tsx.preview b/docs/data/base/components/form-control/BasicFormControl/system/index.tsx.preview
deleted file mode 100644
index 3c87a88033..0000000000
--- a/docs/data/base/components/form-control/BasicFormControl/system/index.tsx.preview
+++ /dev/null
@@ -1,5 +0,0 @@
-
- Name
-
-
-
\ No newline at end of file
diff --git a/docs/data/base/components/form-control/BasicFormControl/tailwind/index.js b/docs/data/base/components/form-control/BasicFormControl/tailwind/index.js
deleted file mode 100644
index 7f0a074965..0000000000
--- a/docs/data/base/components/form-control/BasicFormControl/tailwind/index.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import { FormControl, useFormControlContext } from '@base_ui/react/FormControl';
-import { Input } from '@base_ui/react/Input';
-import { useTheme } from '@mui/system';
-import clsx from 'clsx';
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-export default function BasicFormControl() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
-
- Name
-
-
-
-
- );
-}
-
-const Label = React.forwardRef(({ className: classNameProp, children }, ref) => {
- const formControlContext = useFormControlContext();
- const [dirty, setDirty] = React.useState(false);
-
- React.useEffect(() => {
- if (formControlContext?.filled) {
- setDirty(true);
- }
- }, [formControlContext]);
-
- if (formControlContext === undefined) {
- return {children}
;
- }
-
- const { error, required, filled } = formControlContext;
- const showRequiredError = dirty && required && !filled;
-
- return (
-
- {children}
- {required ? ' *' : ''}
-
- );
-});
-
-const HelperText = React.forwardRef((props, ref) => {
- const { className, ...other } = props;
- const formControlContext = useFormControlContext();
- const [dirty, setDirty] = React.useState(false);
-
- React.useEffect(() => {
- if (formControlContext?.filled) {
- setDirty(true);
- }
- }, [formControlContext]);
-
- if (formControlContext === undefined) {
- return null;
- }
-
- const { required, filled } = formControlContext;
- const showRequiredError = dirty && required && !filled;
-
- return showRequiredError ? (
-
- This field is required.
-
- ) : null;
-});
-
-HelperText.propTypes = {
- className: PropTypes.string,
-};
diff --git a/docs/data/base/components/form-control/BasicFormControl/tailwind/index.tsx b/docs/data/base/components/form-control/BasicFormControl/tailwind/index.tsx
deleted file mode 100644
index 4a5605c133..0000000000
--- a/docs/data/base/components/form-control/BasicFormControl/tailwind/index.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import * as React from 'react';
-import { FormControl, useFormControlContext } from '@base_ui/react/FormControl';
-import { Input } from '@base_ui/react/Input';
-import { useTheme } from '@mui/system';
-import clsx from 'clsx';
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-export default function BasicFormControl() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
-
- Name
-
-
-
-
- );
-}
-
-const Label = React.forwardRef<
- HTMLParagraphElement,
- { className?: string; children?: React.ReactNode }
->(({ className: classNameProp, children }, ref) => {
- const formControlContext = useFormControlContext();
- const [dirty, setDirty] = React.useState(false);
-
- React.useEffect(() => {
- if (formControlContext?.filled) {
- setDirty(true);
- }
- }, [formControlContext]);
-
- if (formControlContext === undefined) {
- return {children}
;
- }
-
- const { error, required, filled } = formControlContext;
- const showRequiredError = dirty && required && !filled;
-
- return (
-
- {children}
- {required ? ' *' : ''}
-
- );
-});
-
-const HelperText = React.forwardRef(
- (props, ref) => {
- const { className, ...other } = props;
- const formControlContext = useFormControlContext();
- const [dirty, setDirty] = React.useState(false);
-
- React.useEffect(() => {
- if (formControlContext?.filled) {
- setDirty(true);
- }
- }, [formControlContext]);
-
- if (formControlContext === undefined) {
- return null;
- }
-
- const { required, filled } = formControlContext;
- const showRequiredError = dirty && required && !filled;
-
- return showRequiredError ? (
-
- This field is required.
-
- ) : null;
- },
-);
diff --git a/docs/data/base/components/form-control/BasicFormControl/tailwind/index.tsx.preview b/docs/data/base/components/form-control/BasicFormControl/tailwind/index.tsx.preview
deleted file mode 100644
index f5d74c426a..0000000000
--- a/docs/data/base/components/form-control/BasicFormControl/tailwind/index.tsx.preview
+++ /dev/null
@@ -1,13 +0,0 @@
-
- Name
-
-
-
\ No newline at end of file
diff --git a/docs/data/base/components/form-control/FormControlFunctionChild.js b/docs/data/base/components/form-control/FormControlFunctionChild.js
deleted file mode 100644
index 5632e22ab0..0000000000
--- a/docs/data/base/components/form-control/FormControlFunctionChild.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import * as React from 'react';
-import { FormControl } from '@base_ui/react/FormControl';
-import { Input, inputClasses } from '@base_ui/react/Input';
-import { styled } from '@mui/system';
-
-export default function FormControlFunctionChild() {
- return (
-
- {({ filled, focused }) => (
-
-
- {filled && !focused && ✔ }
-
- )}
-
- );
-}
-
-const StyledInput = styled(Input)(
- ({ theme }) => `
- display: inline-block;
-
- .${inputClasses.input} {
- width: 320px;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- font-weight: 400;
- line-height: 1.5;
- padding: 8px 12px;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 2px ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus {
- outline: 0;
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
- }
- }
-
- &.filled .${inputClasses.input} {
- box-shadow: 0 0 2px 2px rgba(125, 200, 0, 0.25);
- }
-`,
-);
-
-const OkMark = styled('span')`
- margin-left: 8px;
- margin-top: 10px;
- position: absolute;
- color: rgb(125 200 0 / 1);
-`;
-
-const blue = {
- 100: '#DAECFF',
- 200: '#80BFFF',
- 400: '#3399FF',
- 600: '#0072E5',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
diff --git a/docs/data/base/components/form-control/FormControlFunctionChild.tsx b/docs/data/base/components/form-control/FormControlFunctionChild.tsx
deleted file mode 100644
index 4f33df2001..0000000000
--- a/docs/data/base/components/form-control/FormControlFunctionChild.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import * as React from 'react';
-import { FormControl, FormControlState } from '@base_ui/react/FormControl';
-import { Input, inputClasses } from '@base_ui/react/Input';
-import { styled } from '@mui/system';
-
-export default function FormControlFunctionChild() {
- return (
-
- {({ filled, focused }: FormControlState) => (
-
-
- {filled && !focused && ✔ }
-
- )}
-
- );
-}
-
-const StyledInput = styled(Input)(
- ({ theme }) => `
- display: inline-block;
-
- .${inputClasses.input} {
- width: 320px;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- font-weight: 400;
- line-height: 1.5;
- padding: 8px 12px;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 2px ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus {
- outline: 0;
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
- }
- }
-
- &.filled .${inputClasses.input} {
- box-shadow: 0 0 2px 2px rgba(125, 200, 0, 0.25);
- }
-`,
-);
-
-const OkMark = styled('span')`
- margin-left: 8px;
- margin-top: 10px;
- position: absolute;
- color: rgb(125 200 0 / 1);
-`;
-
-const blue = {
- 100: '#DAECFF',
- 200: '#80BFFF',
- 400: '#3399FF',
- 600: '#0072E5',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
diff --git a/docs/data/base/components/form-control/FormControlFunctionChild.tsx.preview b/docs/data/base/components/form-control/FormControlFunctionChild.tsx.preview
deleted file mode 100644
index d4531fd564..0000000000
--- a/docs/data/base/components/form-control/FormControlFunctionChild.tsx.preview
+++ /dev/null
@@ -1,8 +0,0 @@
-
- {({ filled, focused }: FormControlState) => (
-
-
- {filled && !focused && ✔ }
-
- )}
-
\ No newline at end of file
diff --git a/docs/data/base/components/form-control/UseFormControl.js b/docs/data/base/components/form-control/UseFormControl.js
deleted file mode 100644
index 22b5ec6546..0000000000
--- a/docs/data/base/components/form-control/UseFormControl.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import * as React from 'react';
-import { FormControl, useFormControlContext } from '@base_ui/react/FormControl';
-
-export default function UseFormControl() {
- return (
-
-
-
-
- );
-}
-
-function CustomInput() {
- const formControlContext = useFormControlContext();
-
- if (formControlContext === undefined) {
- return null;
- }
-
- const { value, required, onChange, disabled, onFocus, onBlur } =
- formControlContext;
-
- return (
-
- );
-}
-
-function ControlStateDisplay() {
- const formControlContext = useFormControlContext();
- if (formControlContext === undefined) {
- return null;
- }
-
- const { filled, focused } = formControlContext;
-
- return (
-
- {filled ? 'filled' : 'empty'} |
- {focused ? 'focused' : 'not focused'}
-
- );
-}
diff --git a/docs/data/base/components/form-control/UseFormControl.tsx b/docs/data/base/components/form-control/UseFormControl.tsx
deleted file mode 100644
index 8b70bc0280..0000000000
--- a/docs/data/base/components/form-control/UseFormControl.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import * as React from 'react';
-import { FormControl, useFormControlContext } from '@base_ui/react/FormControl';
-
-export default function UseFormControl() {
- return (
-
-
-
-
- );
-}
-
-function CustomInput() {
- const formControlContext = useFormControlContext();
-
- if (formControlContext === undefined) {
- return null;
- }
-
- const { value, required, onChange, disabled, onFocus, onBlur } =
- formControlContext;
-
- return (
-
- );
-}
-
-function ControlStateDisplay() {
- const formControlContext = useFormControlContext();
- if (formControlContext === undefined) {
- return null;
- }
-
- const { filled, focused } = formControlContext;
-
- return (
-
- {filled ? 'filled' : 'empty'} |
- {focused ? 'focused' : 'not focused'}
-
- );
-}
diff --git a/docs/data/base/components/form-control/UseFormControl.tsx.preview b/docs/data/base/components/form-control/UseFormControl.tsx.preview
deleted file mode 100644
index 611748df31..0000000000
--- a/docs/data/base/components/form-control/UseFormControl.tsx.preview
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/docs/data/base/components/form-control/form-control.md b/docs/data/base/components/form-control/form-control.md
index 208b82479a..53b4b88841 100644
--- a/docs/data/base/components/form-control/form-control.md
+++ b/docs/data/base/components/form-control/form-control.md
@@ -13,98 +13,3 @@ githubLabel: 'component: FormControl'
{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}
{{"component": "modules/components/ComponentPageTabs.js"}}
-
-## Introduction
-
-Form Control is a utility that wraps an input component with other associated components in order to make the state of the input available to those components.
-
-For instance, you may want to show an additional element asking the user to enter a value if the input is empty, or display a warning icon if the entered value is incorrect.
-
-## Component
-
-```jsx
-import { FormControl } from '@base_ui/react/FormControl';
-```
-
-Form Control wraps around the elements of a form that need access to the state of an ` `.
-For instance, if the form's **Submit** button needs to change states after the user enters information, then the component will be structured like this:
-
-```jsx
-
-
- Submit
-
-```
-
-The following demo shows how to create and style a form that uses Form Control to wrap the elements of the form.
-Note that it also uses the `useFormControlContext` hook in order to pass props to the custom Input—see the [Hook](#hook) section below for more details.
-
-{{"demo": "BasicFormControl"}}
-
-### Usage with TypeScript
-
-In TypeScript, you can specify the custom component type used in the `slots.root` as a generic parameter of the unstyled component.
-This way, you can safely provide the custom root's props directly on the component:
-
-```tsx
- slots={{ root: CustomComponent }} customProp />
-```
-
-The same applies for props specific to custom primitive elements:
-
-```tsx
- slots={{ root: 'button' }} onClick={() => {}} />
-```
-
-## Hook
-
-```jsx
-import { useFormControlContext } from '@base_ui/react/FormControl';
-```
-
-The `useFormControlContext` hook reads the context provided by Form Control.
-This hook lets you work with custom input components inside of the Form Control.
-You can also use it to read the form control's state and react to its changes in a custom component.
-
-Hooks _do not_ support [slot props](#custom-structure), but they do support [customization props](#customization).
-
-:::info
-Hooks give you the most room for customization, but require more work to implement.
-With hooks, you can take full control over how your component is rendered, and define all the custom props and CSS classes you need.
-
-You may not need to use hooks unless you find that you're limited by the customization options of their component counterparts—for instance, if your component requires significantly different [structure](#anatomy).
-:::
-
-The demo below shows how to integrate this hook with its component counterpart:
-
-- `CustomInput` is a wrapper around a native HTML ` ` that adds Form Control integration.
-- `ControlStateDisplay` reads the state of the form control and displays it as text.
-
-{{"demo": "UseFormControl.js", "defaultCodeOpen": false}}
-
-Note that even though Form Control supports both controlled and uncontrolled-style APIs (that is it accepts `value` and `defaultValue` props), `useFormControlContext` returns only the controlled `value`.
-This way, you don't have to implement both in your custom input—Form Control does this for you.
-
-:::info
-
-- A component is **controlled** when it's managed by its parent using props.
-- A component is **uncontrolled** when it's managed by its own local state.
-
-Learn more about controlled and uncontrolled components in the [React documentation](https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components).
-:::
-
-## Customization
-
-:::info
-The following features can be used with both components and hooks.
-For the sake of simplicity, demos, and code snippets primarily feature components.
-:::
-
-### Accessing the form control state
-
-You can access the state of the Form Control by providing a function as a child.
-The state will be provided as a parameter to this function.
-
-The following demo shows how to access the state of the Form Control in an Input component nested inside:
-
-{{"demo": "FormControlFunctionChild.js"}}
diff --git a/docs/data/base/components/input/InputAdornments.js b/docs/data/base/components/input/InputAdornments.js
deleted file mode 100644
index 34cc2df7fa..0000000000
--- a/docs/data/base/components/input/InputAdornments.js
+++ /dev/null
@@ -1,186 +0,0 @@
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import { Box, styled } from '@mui/system';
-import { Button } from '@base_ui/react/Button';
-import { Input as BaseInput, inputClasses } from '@base_ui/react/Input';
-import Visibility from '@mui/icons-material/Visibility';
-import VisibilityOff from '@mui/icons-material/VisibilityOff';
-
-const Input = React.forwardRef(function CustomInput(props, ref) {
- const { slots, ...other } = props;
- return (
-
- );
-});
-
-Input.propTypes = {
- /**
- * The components used for each slot inside the InputBase.
- * Either a string to use a HTML element or a component.
- * @default {}
- */
- slots: PropTypes.shape({
- input: PropTypes.elementType,
- root: PropTypes.elementType,
- textarea: PropTypes.elementType,
- }),
-};
-
-export default function InputAdornments() {
- const [values, setValues] = React.useState({
- amount: '',
- password: '',
- weight: '',
- weightRange: '',
- showPassword: false,
- });
-
- const handleChange = (prop) => (event) => {
- setValues({ ...values, [prop]: event.target.value });
- };
-
- const handleClickShowPassword = () => {
- setValues({
- ...values,
- showPassword: !values.showPassword,
- });
- };
-
- const handleMouseDownPassword = (event) => {
- event.preventDefault();
- };
-
- return (
-
- kg}
- />
-
-
- {values.showPassword ? (
-
- ) : (
-
- )}
-
-
- }
- />
-
- );
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#80BFFF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const InputRoot = styled('div')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 400;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
- display: flex;
- align-items: center;
- justify-content: center;
-
-
- &.${inputClasses.focused} {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
- }
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- // firefox
- &:focus-visible {
- outline: 0;
- }
-`,
-);
-
-const InputElement = styled('input')(
- ({ theme }) => `
- font-size: 0.875rem;
- font-family: inherit;
- font-weight: 400;
- line-height: 1.5;
- flex-grow: 1;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: inherit;
- border: none;
- border-radius: inherit;
- padding: 8px 12px;
- outline: 0;
-`,
-);
-
-const IconButton = styled(Button)(
- ({ theme }) => `
- display: inline-flex;
- align-items: center;
- justify-content: center;
- border: none;
- background: inherit;
- cursor: pointer;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[700]};
- `,
-);
-
-const InputAdornment = styled('div')`
- margin: 8px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
-`;
diff --git a/docs/data/base/components/input/InputAdornments.tsx b/docs/data/base/components/input/InputAdornments.tsx
deleted file mode 100644
index a511884ee8..0000000000
--- a/docs/data/base/components/input/InputAdornments.tsx
+++ /dev/null
@@ -1,184 +0,0 @@
-import * as React from 'react';
-import { Box, styled } from '@mui/system';
-import { Button } from '@base_ui/react/Button';
-import { Input as BaseInput, InputProps, inputClasses } from '@base_ui/react/Input';
-import Visibility from '@mui/icons-material/Visibility';
-import VisibilityOff from '@mui/icons-material/VisibilityOff';
-
-const Input = React.forwardRef(function CustomInput(
- props: InputProps,
- ref: React.ForwardedRef,
-) {
- const { slots, ...other } = props;
- return (
-
- );
-});
-
-interface State {
- amount: string;
- password: string;
- weight: string;
- weightRange: string;
- showPassword: boolean;
-}
-
-export default function InputAdornments() {
- const [values, setValues] = React.useState({
- amount: '',
- password: '',
- weight: '',
- weightRange: '',
- showPassword: false,
- });
-
- const handleChange =
- (prop: keyof State) => (event: React.ChangeEvent) => {
- setValues({ ...values, [prop]: event.target.value });
- };
-
- const handleClickShowPassword = () => {
- setValues({
- ...values,
- showPassword: !values.showPassword,
- });
- };
-
- const handleMouseDownPassword = (event: React.MouseEvent) => {
- event.preventDefault();
- };
-
- return (
-
- kg}
- />
-
-
- {values.showPassword ? (
-
- ) : (
-
- )}
-
-
- }
- />
-
- );
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#80BFFF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const InputRoot = styled('div')(
- ({ theme }) => `
- font-family: 'IBM Plex Sans', sans-serif;
- font-weight: 400;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
- display: flex;
- align-items: center;
- justify-content: center;
-
-
- &.${inputClasses.focused} {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
- }
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- // firefox
- &:focus-visible {
- outline: 0;
- }
-`,
-);
-
-const InputElement = styled('input')(
- ({ theme }) => `
- font-size: 0.875rem;
- font-family: inherit;
- font-weight: 400;
- line-height: 1.5;
- flex-grow: 1;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: inherit;
- border: none;
- border-radius: inherit;
- padding: 8px 12px;
- outline: 0;
-`,
-);
-
-const IconButton = styled(Button)(
- ({ theme }) => `
- display: inline-flex;
- align-items: center;
- justify-content: center;
- border: none;
- background: inherit;
- cursor: pointer;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[700]};
- `,
-);
-
-const InputAdornment = styled('div')`
- margin: 8px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
-`;
diff --git a/docs/data/base/components/input/InputMultiline.js b/docs/data/base/components/input/InputMultiline.js
deleted file mode 100644
index 6129128376..0000000000
--- a/docs/data/base/components/input/InputMultiline.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import * as React from 'react';
-import { Input as BaseInput } from '@base_ui/react/Input';
-import { styled } from '@mui/system';
-
-const Input = React.forwardRef(function CustomInput(props, ref) {
- return (
-
- );
-});
-
-export default function InputMultiline() {
- return ;
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#80BFFF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const RootDiv = styled('div')`
- display: flex;
- max-width: 100%;
-`;
-
-const TextareaElement = styled('textarea', {
- shouldForwardProp: (prop) =>
- !['ownerState', 'minRows', 'maxRows'].includes(prop.toString()),
-})(
- ({ theme }) => `
- width: 320px;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- font-weight: 400;
- line-height: 1.5rem;
- padding: 8px 12px;
- border-radius: 8px 8px 0 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]};
- }
-
- // firefox
- &:focus-visible {
- outline: 0;
- }
-`,
-);
diff --git a/docs/data/base/components/input/InputMultiline.tsx b/docs/data/base/components/input/InputMultiline.tsx
deleted file mode 100644
index d810da4c46..0000000000
--- a/docs/data/base/components/input/InputMultiline.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import * as React from 'react';
-import { Input as BaseInput, InputProps } from '@base_ui/react/Input';
-import { styled } from '@mui/system';
-
-const Input = React.forwardRef(function CustomInput(
- props: InputProps,
- ref: React.ForwardedRef,
-) {
- return (
-
- );
-});
-
-export default function InputMultiline() {
- return ;
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#80BFFF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const RootDiv = styled('div')`
- display: flex;
- max-width: 100%;
-`;
-
-const TextareaElement = styled('textarea', {
- shouldForwardProp: (prop) =>
- !['ownerState', 'minRows', 'maxRows'].includes(prop.toString()),
-})(
- ({ theme }) => `
- width: 320px;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- font-weight: 400;
- line-height: 1.5rem;
- padding: 8px 12px;
- border-radius: 8px 8px 0 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]};
- }
-
- // firefox
- &:focus-visible {
- outline: 0;
- }
-`,
-);
diff --git a/docs/data/base/components/input/InputMultiline.tsx.preview b/docs/data/base/components/input/InputMultiline.tsx.preview
deleted file mode 100644
index b454cb1b02..0000000000
--- a/docs/data/base/components/input/InputMultiline.tsx.preview
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/data/base/components/input/InputMultilineAutosize.js b/docs/data/base/components/input/InputMultilineAutosize.js
deleted file mode 100644
index fb74cecf51..0000000000
--- a/docs/data/base/components/input/InputMultilineAutosize.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import * as React from 'react';
-import { Input as BaseInput } from '@base_ui/react/Input';
-import { TextareaAutosize } from '@base_ui/react/TextareaAutosize';
-import { styled } from '@mui/system';
-
-const Input = React.forwardRef(function CustomInput(props, ref) {
- return (
-
- );
-});
-
-export default function InputMultilineAutosize() {
- return ;
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#80BFFF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const RootDiv = styled('div')`
- display: flex;
- max-width: 100%;
-`;
-
-const TextareaElement = styled(TextareaAutosize)(
- ({ theme }) => `
- width: 320px;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- font-weight: 400;
- line-height: 1.5rem;
- padding: 8px 12px;
- border-radius: 8px 8px 0 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]};
- }
-
- // firefox
- &:focus-visible {
- outline: 0;
- }
-`,
-);
diff --git a/docs/data/base/components/input/InputMultilineAutosize.tsx b/docs/data/base/components/input/InputMultilineAutosize.tsx
deleted file mode 100644
index 2d12528519..0000000000
--- a/docs/data/base/components/input/InputMultilineAutosize.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import * as React from 'react';
-import { Input as BaseInput, InputProps } from '@base_ui/react/Input';
-import { TextareaAutosize } from '@base_ui/react/TextareaAutosize';
-import { styled } from '@mui/system';
-
-const Input = React.forwardRef(function CustomInput(
- props: InputProps,
- ref: React.ForwardedRef,
-) {
- return (
-
- );
-});
-
-export default function InputMultilineAutosize() {
- return ;
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#80BFFF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const RootDiv = styled('div')`
- display: flex;
- max-width: 100%;
-`;
-
-const TextareaElement = styled(TextareaAutosize)(
- ({ theme }) => `
- width: 320px;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- font-weight: 400;
- line-height: 1.5rem;
- padding: 8px 12px;
- border-radius: 8px 8px 0 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]};
- }
-
- // firefox
- &:focus-visible {
- outline: 0;
- }
-`,
-);
diff --git a/docs/data/base/components/input/InputMultilineAutosize.tsx.preview b/docs/data/base/components/input/InputMultilineAutosize.tsx.preview
deleted file mode 100644
index b454cb1b02..0000000000
--- a/docs/data/base/components/input/InputMultilineAutosize.tsx.preview
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/data/base/components/input/OTPInput.js b/docs/data/base/components/input/OTPInput.js
deleted file mode 100644
index ac620348c7..0000000000
--- a/docs/data/base/components/input/OTPInput.js
+++ /dev/null
@@ -1,231 +0,0 @@
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import { Input as BaseInput } from '@base_ui/react/Input';
-import { Box, styled } from '@mui/system';
-
-function OTP({ separator, length, value, onChange }) {
- const inputRefs = React.useRef(new Array(length).fill(null));
-
- const focusInput = (targetIndex) => {
- const targetInput = inputRefs.current[targetIndex];
- targetInput.focus();
- };
-
- const selectInput = (targetIndex) => {
- const targetInput = inputRefs.current[targetIndex];
- targetInput.select();
- };
-
- const handleKeyDown = (event, currentIndex) => {
- switch (event.key) {
- case 'ArrowUp':
- case 'ArrowDown':
- case ' ':
- event.preventDefault();
- break;
- case 'ArrowLeft':
- event.preventDefault();
- if (currentIndex > 0) {
- focusInput(currentIndex - 1);
- selectInput(currentIndex - 1);
- }
- break;
- case 'ArrowRight':
- event.preventDefault();
- if (currentIndex < length - 1) {
- focusInput(currentIndex + 1);
- selectInput(currentIndex + 1);
- }
- break;
- case 'Delete':
- event.preventDefault();
- onChange((prevOtp) => {
- const otp =
- prevOtp.slice(0, currentIndex) + prevOtp.slice(currentIndex + 1);
- return otp;
- });
-
- break;
- case 'Backspace':
- event.preventDefault();
- if (currentIndex > 0) {
- focusInput(currentIndex - 1);
- selectInput(currentIndex - 1);
- }
-
- onChange((prevOtp) => {
- const otp =
- prevOtp.slice(0, currentIndex) + prevOtp.slice(currentIndex + 1);
- return otp;
- });
- break;
-
- default:
- break;
- }
- };
-
- const handleChange = (event, currentIndex) => {
- const currentValue = event.target.value;
- let indexToEnter = 0;
-
- while (indexToEnter <= currentIndex) {
- if (inputRefs.current[indexToEnter].value && indexToEnter < currentIndex) {
- indexToEnter += 1;
- } else {
- break;
- }
- }
- onChange((prev) => {
- const otpArray = prev.split('');
- const lastValue = currentValue[currentValue.length - 1];
- otpArray[indexToEnter] = lastValue;
- return otpArray.join('');
- });
- if (currentValue !== '') {
- if (currentIndex < length - 1) {
- focusInput(currentIndex + 1);
- }
- }
- };
-
- const handleClick = (event, currentIndex) => {
- selectInput(currentIndex);
- };
-
- const handlePaste = (event, currentIndex) => {
- event.preventDefault();
- const clipboardData = event.clipboardData;
-
- // Check if there is text data in the clipboard
- if (clipboardData.types.includes('text/plain')) {
- let pastedText = clipboardData.getData('text/plain');
- pastedText = pastedText.substring(0, length).trim();
- let indexToEnter = 0;
-
- while (indexToEnter <= currentIndex) {
- if (inputRefs.current[indexToEnter].value && indexToEnter < currentIndex) {
- indexToEnter += 1;
- } else {
- break;
- }
- }
-
- const otpArray = value.split('');
-
- for (let i = indexToEnter; i < length; i += 1) {
- const lastValue = pastedText[i - indexToEnter] ?? ' ';
- otpArray[i] = lastValue;
- }
-
- onChange(otpArray.join(''));
- }
- };
-
- return (
-
- {new Array(length).fill(null).map((_, index) => (
-
- {
- inputRefs.current[index] = ele;
- },
- onKeyDown: (event) => handleKeyDown(event, index),
- onChange: (event) => handleChange(event, index),
- onClick: (event) => handleClick(event, index),
- onPaste: (event) => handlePaste(event, index),
- value: value[index] ?? '',
- },
- }}
- />
- {index === length - 1 ? null : separator}
-
- ))}
-
- );
-}
-
-OTP.propTypes = {
- length: PropTypes.number.isRequired,
- onChange: PropTypes.func.isRequired,
- separator: PropTypes.node,
- value: PropTypes.string.isRequired,
-};
-
-export default function OTPInput() {
- const [otp, setOtp] = React.useState('');
-
- return (
-
- - } value={otp} onChange={setOtp} length={5} />
- Entered value: {otp}
-
- );
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#80BFFF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const InputElement = styled('input')(
- ({ theme }) => `
- width: 40px;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- font-weight: 400;
- line-height: 1.5;
- padding: 8px 0px;
- border-radius: 8px;
- text-align: center;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
- }
-
- // firefox
- &:focus-visible {
- outline: 0;
- }
-`,
-);
diff --git a/docs/data/base/components/input/OTPInput.tsx b/docs/data/base/components/input/OTPInput.tsx
deleted file mode 100644
index 84dd43be1e..0000000000
--- a/docs/data/base/components/input/OTPInput.tsx
+++ /dev/null
@@ -1,245 +0,0 @@
-import * as React from 'react';
-import { Input as BaseInput } from '@base_ui/react/Input';
-import { Box, styled } from '@mui/system';
-
-function OTP({
- separator,
- length,
- value,
- onChange,
-}: {
- separator: React.ReactNode;
- length: number;
- value: string;
- onChange: React.Dispatch>;
-}) {
- const inputRefs = React.useRef(new Array(length).fill(null));
-
- const focusInput = (targetIndex: number) => {
- const targetInput = inputRefs.current[targetIndex];
- targetInput.focus();
- };
-
- const selectInput = (targetIndex: number) => {
- const targetInput = inputRefs.current[targetIndex];
- targetInput.select();
- };
-
- const handleKeyDown = (
- event: React.KeyboardEvent,
- currentIndex: number,
- ) => {
- switch (event.key) {
- case 'ArrowUp':
- case 'ArrowDown':
- case ' ':
- event.preventDefault();
- break;
- case 'ArrowLeft':
- event.preventDefault();
- if (currentIndex > 0) {
- focusInput(currentIndex - 1);
- selectInput(currentIndex - 1);
- }
- break;
- case 'ArrowRight':
- event.preventDefault();
- if (currentIndex < length - 1) {
- focusInput(currentIndex + 1);
- selectInput(currentIndex + 1);
- }
- break;
- case 'Delete':
- event.preventDefault();
- onChange((prevOtp) => {
- const otp =
- prevOtp.slice(0, currentIndex) + prevOtp.slice(currentIndex + 1);
- return otp;
- });
-
- break;
- case 'Backspace':
- event.preventDefault();
- if (currentIndex > 0) {
- focusInput(currentIndex - 1);
- selectInput(currentIndex - 1);
- }
-
- onChange((prevOtp) => {
- const otp =
- prevOtp.slice(0, currentIndex) + prevOtp.slice(currentIndex + 1);
- return otp;
- });
- break;
-
- default:
- break;
- }
- };
-
- const handleChange = (
- event: React.ChangeEvent,
- currentIndex: number,
- ) => {
- const currentValue = event.target.value;
- let indexToEnter = 0;
-
- while (indexToEnter <= currentIndex) {
- if (inputRefs.current[indexToEnter].value && indexToEnter < currentIndex) {
- indexToEnter += 1;
- } else {
- break;
- }
- }
- onChange((prev) => {
- const otpArray = prev.split('');
- const lastValue = currentValue[currentValue.length - 1];
- otpArray[indexToEnter] = lastValue;
- return otpArray.join('');
- });
- if (currentValue !== '') {
- if (currentIndex < length - 1) {
- focusInput(currentIndex + 1);
- }
- }
- };
-
- const handleClick = (
- event: React.MouseEvent,
- currentIndex: number,
- ) => {
- selectInput(currentIndex);
- };
-
- const handlePaste = (
- event: React.ClipboardEvent,
- currentIndex: number,
- ) => {
- event.preventDefault();
- const clipboardData = event.clipboardData;
-
- // Check if there is text data in the clipboard
- if (clipboardData.types.includes('text/plain')) {
- let pastedText = clipboardData.getData('text/plain');
- pastedText = pastedText.substring(0, length).trim();
- let indexToEnter = 0;
-
- while (indexToEnter <= currentIndex) {
- if (inputRefs.current[indexToEnter].value && indexToEnter < currentIndex) {
- indexToEnter += 1;
- } else {
- break;
- }
- }
-
- const otpArray = value.split('');
-
- for (let i = indexToEnter; i < length; i += 1) {
- const lastValue = pastedText[i - indexToEnter] ?? ' ';
- otpArray[i] = lastValue;
- }
-
- onChange(otpArray.join(''));
- }
- };
-
- return (
-
- {new Array(length).fill(null).map((_, index) => (
-
- {
- inputRefs.current[index] = ele!;
- },
- onKeyDown: (event) => handleKeyDown(event, index),
- onChange: (event) => handleChange(event, index),
- onClick: (event) => handleClick(event, index),
- onPaste: (event) => handlePaste(event, index),
- value: value[index] ?? '',
- },
- }}
- />
- {index === length - 1 ? null : separator}
-
- ))}
-
- );
-}
-
-export default function OTPInput() {
- const [otp, setOtp] = React.useState('');
-
- return (
-
- -} value={otp} onChange={setOtp} length={5} />
- Entered value: {otp}
-
- );
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#80BFFF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const InputElement = styled('input')(
- ({ theme }) => `
- width: 40px;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- font-weight: 400;
- line-height: 1.5;
- padding: 8px 0px;
- border-radius: 8px;
- text-align: center;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
- }
-
- // firefox
- &:focus-visible {
- outline: 0;
- }
-`,
-);
diff --git a/docs/data/base/components/input/OTPInput.tsx.preview b/docs/data/base/components/input/OTPInput.tsx.preview
deleted file mode 100644
index 9e5f02b247..0000000000
--- a/docs/data/base/components/input/OTPInput.tsx.preview
+++ /dev/null
@@ -1,2 +0,0 @@
--} value={otp} onChange={setOtp} length={5} />
-Entered value: {otp}
\ No newline at end of file
diff --git a/docs/data/base/components/input/UnstyledInputBasic/css/index.js b/docs/data/base/components/input/UnstyledInputBasic/css/index.js
deleted file mode 100644
index d84c04bea7..0000000000
--- a/docs/data/base/components/input/UnstyledInputBasic/css/index.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import * as React from 'react';
-import { Input } from '@base_ui/react/Input';
-import { useTheme } from '@mui/system';
-
-export default function UnstyledInputBasic() {
- return (
-
-
-
-
- );
-}
-
-const cyan = {
- 50: '#E9F8FC',
- 100: '#BDEBF4',
- 200: '#99D8E5',
- 300: '#66BACC',
- 400: '#1F94AD',
- 500: '#0D5463',
- 600: '#094855',
- 700: '#063C47',
- 800: '#043039',
- 900: '#022127',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-function Styles() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
- );
-}
diff --git a/docs/data/base/components/input/UnstyledInputBasic/css/index.tsx b/docs/data/base/components/input/UnstyledInputBasic/css/index.tsx
deleted file mode 100644
index d84c04bea7..0000000000
--- a/docs/data/base/components/input/UnstyledInputBasic/css/index.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import * as React from 'react';
-import { Input } from '@base_ui/react/Input';
-import { useTheme } from '@mui/system';
-
-export default function UnstyledInputBasic() {
- return (
-
-
-
-
- );
-}
-
-const cyan = {
- 50: '#E9F8FC',
- 100: '#BDEBF4',
- 200: '#99D8E5',
- 300: '#66BACC',
- 400: '#1F94AD',
- 500: '#0D5463',
- 600: '#094855',
- 700: '#063C47',
- 800: '#043039',
- 900: '#022127',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-function Styles() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
- );
-}
diff --git a/docs/data/base/components/input/UnstyledInputBasic/css/index.tsx.preview b/docs/data/base/components/input/UnstyledInputBasic/css/index.tsx.preview
deleted file mode 100644
index b4003b693f..0000000000
--- a/docs/data/base/components/input/UnstyledInputBasic/css/index.tsx.preview
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/docs/data/base/components/input/UnstyledInputBasic/system/index.js b/docs/data/base/components/input/UnstyledInputBasic/system/index.js
deleted file mode 100644
index c68cd2475c..0000000000
--- a/docs/data/base/components/input/UnstyledInputBasic/system/index.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import * as React from 'react';
-import { Input as BaseInput } from '@base_ui/react/Input';
-import { styled } from '@mui/system';
-
-const Input = React.forwardRef(function CustomInput(props, ref) {
- return ;
-});
-
-export default function UnstyledInputBasic() {
- return ;
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#80BFFF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const InputElement = styled('input')(
- ({ theme }) => `
- width: 320px;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- font-weight: 400;
- line-height: 1.5;
- padding: 8px 12px;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 2px ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
- }
-
- // firefox
- &:focus-visible {
- outline: 0;
- }
-`,
-);
diff --git a/docs/data/base/components/input/UnstyledInputBasic/system/index.tsx b/docs/data/base/components/input/UnstyledInputBasic/system/index.tsx
deleted file mode 100644
index d08ed64836..0000000000
--- a/docs/data/base/components/input/UnstyledInputBasic/system/index.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import * as React from 'react';
-import { Input as BaseInput, InputProps } from '@base_ui/react/Input';
-import { styled } from '@mui/system';
-
-const Input = React.forwardRef(function CustomInput(
- props: InputProps,
- ref: React.ForwardedRef,
-) {
- return ;
-});
-
-export default function UnstyledInputBasic() {
- return ;
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#80BFFF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const InputElement = styled('input')(
- ({ theme }) => `
- width: 320px;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- font-weight: 400;
- line-height: 1.5;
- padding: 8px 12px;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 2px ${theme.palette.mode === 'dark' ? grey[900] : grey[50]};
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
- }
-
- // firefox
- &:focus-visible {
- outline: 0;
- }
-`,
-);
diff --git a/docs/data/base/components/input/UnstyledInputBasic/system/index.tsx.preview b/docs/data/base/components/input/UnstyledInputBasic/system/index.tsx.preview
deleted file mode 100644
index 12d25a9fa9..0000000000
--- a/docs/data/base/components/input/UnstyledInputBasic/system/index.tsx.preview
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.js b/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.js
deleted file mode 100644
index cd8af58451..0000000000
--- a/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import * as React from 'react';
-import { Input } from '@base_ui/react/Input';
-import { useTheme } from '@mui/system';
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-export default function UnstyledInputBasic() {
- // Replace this with your app logic for determining dark modes
- const isDarkMode = useIsDarkMode();
-
- return (
-
- );
-}
diff --git a/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.tsx b/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.tsx
deleted file mode 100644
index cd8af58451..0000000000
--- a/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import * as React from 'react';
-import { Input } from '@base_ui/react/Input';
-import { useTheme } from '@mui/system';
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-export default function UnstyledInputBasic() {
- // Replace this with your app logic for determining dark modes
- const isDarkMode = useIsDarkMode();
-
- return (
-
- );
-}
diff --git a/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.tsx.preview b/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.tsx.preview
deleted file mode 100644
index 2431f649ca..0000000000
--- a/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.tsx.preview
+++ /dev/null
@@ -1,11 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/css/index.js b/docs/data/base/components/input/UnstyledInputIntroduction/css/index.js
deleted file mode 100644
index 22aa3be103..0000000000
--- a/docs/data/base/components/input/UnstyledInputIntroduction/css/index.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import * as React from 'react';
-import { Input } from '@base_ui/react/Input';
-import { useTheme } from '@mui/system';
-
-export default function UnstyledInputIntroduction() {
- return (
-
-
-
-
- );
-}
-
-const cyan = {
- 50: '#E9F8FC',
- 100: '#BDEBF4',
- 200: '#99D8E5',
- 300: '#66BACC',
- 400: '#1F94AD',
- 500: '#0D5463',
- 600: '#094855',
- 700: '#063C47',
- 800: '#043039',
- 900: '#022127',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-function Styles() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
- );
-}
diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/css/index.tsx b/docs/data/base/components/input/UnstyledInputIntroduction/css/index.tsx
deleted file mode 100644
index 22aa3be103..0000000000
--- a/docs/data/base/components/input/UnstyledInputIntroduction/css/index.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import * as React from 'react';
-import { Input } from '@base_ui/react/Input';
-import { useTheme } from '@mui/system';
-
-export default function UnstyledInputIntroduction() {
- return (
-
-
-
-
- );
-}
-
-const cyan = {
- 50: '#E9F8FC',
- 100: '#BDEBF4',
- 200: '#99D8E5',
- 300: '#66BACC',
- 400: '#1F94AD',
- 500: '#0D5463',
- 600: '#094855',
- 700: '#063C47',
- 800: '#043039',
- 900: '#022127',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-function Styles() {
- // Replace this with your app logic for determining dark mode
- const isDarkMode = useIsDarkMode();
-
- return (
-
- );
-}
diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/css/index.tsx.preview b/docs/data/base/components/input/UnstyledInputIntroduction/css/index.tsx.preview
deleted file mode 100644
index 7ae3c657ea..0000000000
--- a/docs/data/base/components/input/UnstyledInputIntroduction/css/index.tsx.preview
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/system/index.js b/docs/data/base/components/input/UnstyledInputIntroduction/system/index.js
deleted file mode 100644
index 559418a247..0000000000
--- a/docs/data/base/components/input/UnstyledInputIntroduction/system/index.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import * as React from 'react';
-import { Input as BaseInput } from '@base_ui/react/Input';
-import { styled } from '@mui/system';
-
-const Input = React.forwardRef(function CustomInput(props, ref) {
- return ;
-});
-
-export default function UnstyledInputIntroduction() {
- return ;
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#b6daff',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 900: '#003A75',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const InputElement = styled('input')(
- ({ theme }) => `
- width: 320px;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- font-weight: 400;
- line-height: 1.5;
- padding: 8px 12px;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
- }
-
- // firefox
- &:focus-visible {
- outline: 0;
- }
-`,
-);
diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/system/index.tsx b/docs/data/base/components/input/UnstyledInputIntroduction/system/index.tsx
deleted file mode 100644
index ac2e53fcd0..0000000000
--- a/docs/data/base/components/input/UnstyledInputIntroduction/system/index.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import * as React from 'react';
-import { Input as BaseInput } from '@base_ui/react/Input';
-import { styled } from '@mui/system';
-
-const Input = React.forwardRef(function CustomInput(
- props: React.InputHTMLAttributes,
- ref: React.ForwardedRef,
-) {
- return ;
-});
-
-export default function UnstyledInputIntroduction() {
- return ;
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#b6daff',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 900: '#003A75',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const InputElement = styled('input')(
- ({ theme }) => `
- width: 320px;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- font-weight: 400;
- line-height: 1.5;
- padding: 8px 12px;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
- }
-
- // firefox
- &:focus-visible {
- outline: 0;
- }
-`,
-);
diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/system/index.tsx.preview b/docs/data/base/components/input/UnstyledInputIntroduction/system/index.tsx.preview
deleted file mode 100644
index 12d25a9fa9..0000000000
--- a/docs/data/base/components/input/UnstyledInputIntroduction/system/index.tsx.preview
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.js b/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.js
deleted file mode 100644
index df6a738e74..0000000000
--- a/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import { Input as BaseInput } from '@base_ui/react/Input';
-import { useTheme } from '@mui/system';
-import clsx from 'clsx';
-
-export default function UnstyledInputIntroduction() {
- return ;
-}
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-const resolveSlotProps = (fn, args) => (typeof fn === 'function' ? fn(args) : fn);
-
-const Input = React.forwardRef((props, ref) => {
- // Replace this with your app logic for determining dark modes
- const isDarkMode = useIsDarkMode();
-
- return (
- {
- const resolvedSlotProps = resolveSlotProps(
- props.slotProps?.input,
- ownerState,
- );
- return {
- ...resolvedSlotProps,
- className: clsx(
- 'w-80 text-sm font-normal font-sans leading-5 px-3 py-2 rounded-lg shadow-md shadow-slate-100 dark:shadow-slate-900 focus:shadow-outline-purple dark:focus:shadow-outline-purple dark:outline-purple-600 focus:shadow-lg border border-solid border-slate-300 hover:border-purple-500 dark:hover:border-purple-500 focus:border-purple-500 dark:focus:border-purple-600 dark:border-slate-600 bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-300 focus-visible:outline-0',
- resolvedSlotProps?.className,
- ),
- };
- },
- }}
- />
- );
-});
-
-Input.propTypes = {
- /**
- * Class name applied to the root element.
- */
- className: PropTypes.string,
- /**
- * The props used for each slot inside the Input.
- * @default {}
- */
- slotProps: PropTypes.shape({
- input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
- root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
- }),
-};
diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.tsx b/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.tsx
deleted file mode 100644
index ee54d299b2..0000000000
--- a/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import * as React from 'react';
-import { Input as BaseInput, InputProps } from '@base_ui/react/Input';
-import { useTheme } from '@mui/system';
-import clsx from 'clsx';
-
-export default function UnstyledInputIntroduction() {
- return ;
-}
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-const resolveSlotProps = (fn: any, args: any) =>
- typeof fn === 'function' ? fn(args) : fn;
-
-const Input = React.forwardRef((props, ref) => {
- // Replace this with your app logic for determining dark modes
- const isDarkMode = useIsDarkMode();
-
- return (
- {
- const resolvedSlotProps = resolveSlotProps(
- props.slotProps?.input,
- ownerState,
- );
- return {
- ...resolvedSlotProps,
- className: clsx(
- 'w-80 text-sm font-normal font-sans leading-5 px-3 py-2 rounded-lg shadow-md shadow-slate-100 dark:shadow-slate-900 focus:shadow-outline-purple dark:focus:shadow-outline-purple dark:outline-purple-600 focus:shadow-lg border border-solid border-slate-300 hover:border-purple-500 dark:hover:border-purple-500 focus:border-purple-500 dark:focus:border-purple-600 dark:border-slate-600 bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-300 focus-visible:outline-0',
- resolvedSlotProps?.className,
- ),
- };
- },
- }}
- />
- );
-});
diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.tsx.preview b/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.tsx.preview
deleted file mode 100644
index 12d25a9fa9..0000000000
--- a/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.tsx.preview
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/data/base/components/input/UseInput.js b/docs/data/base/components/input/UseInput.js
deleted file mode 100644
index eeac15d8e4..0000000000
--- a/docs/data/base/components/input/UseInput.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import * as React from 'react';
-import { useInput } from '@base_ui/react/useInput';
-import { styled } from '@mui/system';
-import { unstable_useForkRef as useForkRef } from '@mui/utils';
-
-const CustomInput = React.forwardRef(function CustomInput(props, ref) {
- const { getRootProps, getInputProps } = useInput(props);
-
- const inputProps = getInputProps();
-
- // Make sure that both the forwarded ref and the ref returned from the getInputProps are applied on the input element
- inputProps.ref = useForkRef(inputProps.ref, ref);
-
- return (
-
-
-
- );
-});
-
-export default function UseInput() {
- return ;
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#80BFFF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const StyledInputElement = styled('input')(
- ({ theme }) => `
- width: 320px;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- font-weight: 400;
- line-height: 1.5;
- padding: 8px 12px;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
- }
-
- // firefox
- &:focus-visible {
- outline: 0;
- }
-`,
-);
diff --git a/docs/data/base/components/input/UseInput.tsx b/docs/data/base/components/input/UseInput.tsx
deleted file mode 100644
index 9e8a8f4ffc..0000000000
--- a/docs/data/base/components/input/UseInput.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import * as React from 'react';
-import { useInput } from '@base_ui/react/useInput';
-import { styled } from '@mui/system';
-import { unstable_useForkRef as useForkRef } from '@mui/utils';
-
-const CustomInput = React.forwardRef(function CustomInput(
- props: React.InputHTMLAttributes,
- ref: React.ForwardedRef,
-) {
- const { getRootProps, getInputProps } = useInput(props);
-
- const inputProps = getInputProps();
-
- // Make sure that both the forwarded ref and the ref returned from the getInputProps are applied on the input element
- inputProps.ref = useForkRef(inputProps.ref, ref);
-
- return (
-
-
-
- );
-});
-
-export default function UseInput() {
- return ;
-}
-
-const blue = {
- 100: '#DAECFF',
- 200: '#80BFFF',
- 400: '#3399FF',
- 500: '#007FFF',
- 600: '#0072E5',
- 700: '#0059B2',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-const StyledInputElement = styled('input')(
- ({ theme }) => `
- width: 320px;
- font-family: 'IBM Plex Sans', sans-serif;
- font-size: 0.875rem;
- font-weight: 400;
- line-height: 1.5;
- padding: 8px 12px;
- border-radius: 8px;
- color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
- border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- box-shadow: 0px 2px 4px ${
- theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
- };
-
- &:hover {
- border-color: ${blue[400]};
- }
-
- &:focus {
- border-color: ${blue[400]};
- box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
- }
-
- // firefox
- &:focus-visible {
- outline: 0;
- }
-`,
-);
diff --git a/docs/data/base/components/input/UseInput.tsx.preview b/docs/data/base/components/input/UseInput.tsx.preview
deleted file mode 100644
index bd1c548432..0000000000
--- a/docs/data/base/components/input/UseInput.tsx.preview
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/data/base/components/input/input.md b/docs/data/base/components/input/input.md
deleted file mode 100644
index 4bd7d81492..0000000000
--- a/docs/data/base/components/input/input.md
+++ /dev/null
@@ -1,144 +0,0 @@
----
-productId: base-ui
-title: React Input component and hook
-components: Input
-hooks: useInput
-githubLabel: 'component: input'
----
-
-# Input
-
-The Input component provides users with a field to enter and edit text.
-
-{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}
-
-{{"component": "modules/components/ComponentPageTabs.js"}}
-
-## Introduction
-
-An input is a UI element that accepts text data from the user.
-The Input component replaces the native HTML ` ` tag, and offers expanded customization and accessibility features.
-It can also be transformed into a `