diff --git a/docs/src/app/layout.tsx b/docs/src/app/layout.tsx
index 23def0a3e0..8389157842 100644
--- a/docs/src/app/layout.tsx
+++ b/docs/src/app/layout.tsx
@@ -19,7 +19,7 @@ export default function Layout({ children }: React.PropsWithChildren) {
/>
diff --git a/docs/src/app/new/(content)/components/accordion/demos/hero/css-modules/index.module.css b/docs/src/app/new/(content)/components/accordion/demos/hero/css-modules/index.module.css
new file mode 100644
index 0000000000..10aef9b783
--- /dev/null
+++ b/docs/src/app/new/(content)/components/accordion/demos/hero/css-modules/index.module.css
@@ -0,0 +1,71 @@
+.Root {
+ box-sizing: border-box;
+ display: flex;
+ min-height: 12rem;
+ width: 24rem;
+ max-width: calc(100vw - 8rem);
+ flex-direction: column;
+ justify-content: center;
+}
+
+.Item {
+ border-bottom: 1px solid var(--color-gray-200);
+}
+
+.Header {
+ margin: 0;
+}
+
+.Trigger {
+ box-sizing: border-box;
+ display: flex;
+ width: 100%;
+ align-items: baseline;
+ justify-content: space-between;
+ padding: 0.5rem 0;
+ font: inherit;
+ font-weight: 500;
+ font-size: 1rem;
+ line-height: 1.5rem;
+ letter-spacing: 0em;
+ background: none;
+ border: none;
+ outline: none;
+ cursor: pointer;
+
+ &:focus-visible {
+ outline: 2px solid var(--color-blue);
+ }
+}
+
+.TriggerIcon {
+ box-sizing: border-box;
+ width: 0.75rem;
+ height: 0.75rem;
+ margin-right: 0.5rem;
+ transition: transform 150ms ease-out;
+
+ [data-panel-open] > & {
+ transform: rotate(45deg) scale(1.1);
+ }
+}
+
+.Panel {
+ box-sizing: border-box;
+ height: var(--accordion-panel-height);
+ overflow: hidden;
+ color: var(--color-gray-600);
+ font-size: 0.9375rem;
+ line-height: 1.375rem;
+ letter-spacing: 0.001em;
+ transition: height 150ms ease-out;
+
+ &[data-entering],
+ &[data-exiting] {
+ height: 0;
+ }
+}
+
+.Content {
+ padding-bottom: 0.5rem;
+}
diff --git a/docs/src/app/new/(content)/components/accordion/demos/hero/css-modules/index.tsx b/docs/src/app/new/(content)/components/accordion/demos/hero/css-modules/index.tsx
new file mode 100644
index 0000000000..4ed5cfaafd
--- /dev/null
+++ b/docs/src/app/new/(content)/components/accordion/demos/hero/css-modules/index.tsx
@@ -0,0 +1,61 @@
+import * as React from 'react';
+import { Accordion } from '@base-ui-components/react/accordion';
+import styles from './index.module.css';
+
+export default function ExampleAccordion() {
+ return (
+
+
+
+
+ What is Base UI?
+
+
+
+
+
+ Base UI is a library of high-quality, accessible, unstyled React
+ components for design systems and web apps.
+
+
+
+
+
+
+
+ How do I get started?
+
+
+
+
+
+ Head to the “Quick start” guide in the docs. If you’ve used unstyled
+ libraries before, you’ll feel right at home.
+
+
+
+
+
+
+
+ Can I use it for my next project?
+
+
+
+
+
+ Of course! Base UI is free and open source.
+
+
+
+
+ );
+}
+
+function PlusIcon(props: React.ComponentProps<'svg'>) {
+ return (
+
+
+
+ );
+}
diff --git a/docs/src/app/new/(content)/components/accordion/demos/hero/index.ts b/docs/src/app/new/(content)/components/accordion/demos/hero/index.ts
new file mode 100644
index 0000000000..80097d6015
--- /dev/null
+++ b/docs/src/app/new/(content)/components/accordion/demos/hero/index.ts
@@ -0,0 +1,3 @@
+'use client';
+export { default as CssModules } from './css-modules';
+export { default as Tailwind } from './tailwind';
diff --git a/docs/src/app/new/(content)/components/accordion/demos/hero/tailwind/index.tsx b/docs/src/app/new/(content)/components/accordion/demos/hero/tailwind/index.tsx
new file mode 100644
index 0000000000..f9d03b6dd1
--- /dev/null
+++ b/docs/src/app/new/(content)/components/accordion/demos/hero/tailwind/index.tsx
@@ -0,0 +1,58 @@
+import * as React from 'react';
+import { Accordion } from '@base-ui-components/react/accordion';
+
+export default function ExampleAccordion() {
+ return (
+
+
+
+
+ What is Base UI?
+
+
+
+
+
+ Base UI is a library of high-quality, accessible, unstyled React
+ components for design systems and web apps.
+
+
+
+
+
+
+
+ How do I get started?
+
+
+
+
+
+ Head to the “Quick start” guide in the docs. If you’ve used unstyled
+ libraries before, you’ll feel right at home.
+
+
+
+
+
+
+
+ Can I use it for my next project?
+
+
+
+
+ Of course! Base UI is free and open source.
+
+
+
+ );
+}
+
+function PlusIcon(props: React.ComponentProps<'svg'>) {
+ return (
+
+
+
+ );
+}
diff --git a/docs/src/app/new/(content)/components/accordion/page.mdx b/docs/src/app/new/(content)/components/accordion/page.mdx
new file mode 100644
index 0000000000..aec5846767
--- /dev/null
+++ b/docs/src/app/new/(content)/components/accordion/page.mdx
@@ -0,0 +1,25 @@
+# Accordion
+
+A set of collapsible panels with headings.
+
+
+
+
+## API reference
+
+Import the component and place its parts the following way:
+
+```jsx title="Anatomy"
+import { Accordion } from '@base-ui-components/react/accordion';
+
+
+
+
+
+
+
+
+ ;
+```
+
+
diff --git a/docs/src/app/new/(content)/components/alert-dialog/demos/hero/css-modules/index.module.css b/docs/src/app/new/(content)/components/alert-dialog/demos/hero/css-modules/index.module.css
new file mode 100644
index 0000000000..91f4db5eb1
--- /dev/null
+++ b/docs/src/app/new/(content)/components/alert-dialog/demos/hero/css-modules/index.module.css
@@ -0,0 +1,93 @@
+.Button {
+ box-sizing: border-box;
+ display: flex;
+ padding: 0.5rem 0.875rem;
+ margin: 0;
+ border: none;
+ border-radius: 0.375rem;
+ background-color: var(--color-gray-50);
+ font: inherit;
+ font-weight: 500;
+ color: var(--color-gray-900);
+ outline: 1px solid var(--color-gray-200);
+ user-select: none;
+
+ &[data-color='red'] {
+ color: var(--color-red);
+ }
+
+ @media (hover: hover) {
+ &:hover {
+ background-color: var(--color-gray-100);
+ }
+ }
+
+ &:focus-visible {
+ outline: 2px solid var(--color-blue);
+ }
+
+ &:active {
+ background-color: var(--color-gray-100);
+ }
+}
+
+.Backdrop {
+ position: fixed;
+ inset: 0;
+ background-color: black;
+ opacity: 0.2;
+ transition: opacity 150ms;
+
+ @media (prefers-color-scheme: dark) {
+ opacity: 0.7;
+ }
+
+ &[data-entering],
+ &[data-exiting] {
+ opacity: 0;
+ }
+}
+
+.Popup {
+ box-sizing: border-box;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 24rem;
+ max-width: calc(100vw - 3rem);
+ margin-top: -2rem;
+ padding: 1.5rem;
+ border-radius: 0.5rem;
+ border: 1px solid var(--color-gray-300);
+ background-color: var(--color-gray-50);
+ color: var(--color-gray-950);
+ outline: 0;
+ transition: all 150ms;
+
+ &[data-entering],
+ &[data-exiting] {
+ opacity: 0;
+ transform: translate(-50%, -50%) scale(0.9);
+ }
+}
+
+.Title {
+ margin-top: -0.375rem;
+ margin-bottom: 0.25rem;
+ font-size: 1.125rem;
+ line-height: 1.75rem;
+ letter-spacing: -0.0025em;
+ font-weight: 500;
+}
+
+.Description {
+ margin: 0 0 1.5rem;
+ color: var(--color-gray-600);
+}
+
+.Actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 1rem;
+}
diff --git a/docs/src/app/new/(content)/components/alert-dialog/demos/hero/css-modules/index.tsx b/docs/src/app/new/(content)/components/alert-dialog/demos/hero/css-modules/index.tsx
new file mode 100644
index 0000000000..67e7fca52f
--- /dev/null
+++ b/docs/src/app/new/(content)/components/alert-dialog/demos/hero/css-modules/index.tsx
@@ -0,0 +1,28 @@
+import * as React from 'react';
+import { AlertDialog } from '@base-ui-components/react/alert-dialog';
+import styles from './index.module.css';
+
+export default function ExampleAlertDialog() {
+ return (
+
+
+ Discard draft
+
+
+
+
+ Discard draft?
+
+
+ You can't undo this action.
+
+
+
+
+ );
+}
diff --git a/docs/src/app/new/(content)/components/alert-dialog/demos/hero/index.ts b/docs/src/app/new/(content)/components/alert-dialog/demos/hero/index.ts
new file mode 100644
index 0000000000..80097d6015
--- /dev/null
+++ b/docs/src/app/new/(content)/components/alert-dialog/demos/hero/index.ts
@@ -0,0 +1,3 @@
+'use client';
+export { default as CssModules } from './css-modules';
+export { default as Tailwind } from './tailwind';
diff --git a/docs/src/app/new/(content)/components/alert-dialog/demos/hero/tailwind/index.tsx b/docs/src/app/new/(content)/components/alert-dialog/demos/hero/tailwind/index.tsx
new file mode 100644
index 0000000000..46aa816b02
--- /dev/null
+++ b/docs/src/app/new/(content)/components/alert-dialog/demos/hero/tailwind/index.tsx
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import { AlertDialog } from '@base-ui-components/react/alert-dialog';
+
+export default function ExampleAlertDialog() {
+ return (
+
+
+ Discard draft
+
+
+
+
+ Discard draft?
+
+
+ You can’t undo this action.
+
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+ );
+}
diff --git a/docs/src/app/new/(content)/components/alert-dialog/page.mdx b/docs/src/app/new/(content)/components/alert-dialog/page.mdx
new file mode 100644
index 0000000000..87620deac8
--- /dev/null
+++ b/docs/src/app/new/(content)/components/alert-dialog/page.mdx
@@ -0,0 +1,26 @@
+# Alert Dialog
+
+A dialog that requires user response to proceed.
+
+
+
+
+## API reference
+
+Import the component and place its parts the following way:
+
+```jsx title="Anatomy"
+import { AlertDialog } from '@base-ui-components/react/alert-dialog';
+
+
+
+
+
+
+
+
+
+ ;
+```
+
+
diff --git a/docs/src/app/new/(content)/components/checkbox-group/demos/hero/css-modules/index.module.css b/docs/src/app/new/(content)/components/checkbox-group/demos/hero/css-modules/index.module.css
new file mode 100644
index 0000000000..67dd06d4e1
--- /dev/null
+++ b/docs/src/app/new/(content)/components/checkbox-group/demos/hero/css-modules/index.module.css
@@ -0,0 +1,58 @@
+.Group {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.Label {
+ font-weight: 500;
+}
+
+.Item {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.Checkbox {
+ box-sizing: border-box;
+ display: flex;
+ width: 1.25rem;
+ height: 1.25rem;
+ align-items: center;
+ justify-content: center;
+ border-radius: 0.25rem;
+ outline: 0;
+ padding: 0;
+ margin: 0;
+ border: none;
+
+ &[data-checked] {
+ background-color: var(--color-gray-900);
+ }
+
+ &[data-unchecked] {
+ border: 1px solid var(--color-gray-300);
+ background-color: transparent;
+ }
+
+ &:focus-visible {
+ outline: 2px solid var(--color-blue);
+ outline-offset: 2px;
+ }
+}
+
+.Indicator {
+ width: 0.75rem;
+ height: 0.75rem;
+ color: var(--color-gray-50);
+
+ &[data-unchecked] {
+ display: none;
+ }
+}
+
+.Icon {
+ width: 100%;
+ height: 100%;
+}
diff --git a/docs/src/app/new/(content)/components/checkbox-group/demos/hero/css-modules/index.tsx b/docs/src/app/new/(content)/components/checkbox-group/demos/hero/css-modules/index.tsx
new file mode 100644
index 0000000000..7f3333e3d6
--- /dev/null
+++ b/docs/src/app/new/(content)/components/checkbox-group/demos/hero/css-modules/index.tsx
@@ -0,0 +1,57 @@
+import * as React from 'react';
+import { Checkbox } from '@base-ui-components/react/checkbox';
+import { CheckboxGroup } from '@base-ui-components/react/checkbox-group';
+import styles from './index.module.css';
+
+export default function ExampleCheckboxGroup() {
+ return (
+
+
+ Apples
+
+
+
+
+
+
+
+
+ Fuji
+
+
+
+
+
+
+
+
+ Gala
+
+
+
+
+
+
+
+
+ Granny Smith
+
+
+ );
+}
+
+function CheckIcon(props: React.ComponentProps<'svg'>) {
+ return (
+
+
+
+ );
+}
diff --git a/docs/src/app/new/(content)/components/checkbox-group/demos/hero/index.ts b/docs/src/app/new/(content)/components/checkbox-group/demos/hero/index.ts
new file mode 100644
index 0000000000..80097d6015
--- /dev/null
+++ b/docs/src/app/new/(content)/components/checkbox-group/demos/hero/index.ts
@@ -0,0 +1,3 @@
+'use client';
+export { default as CssModules } from './css-modules';
+export { default as Tailwind } from './tailwind';
diff --git a/docs/src/app/new/(content)/components/checkbox-group/demos/hero/tailwind/index.tsx b/docs/src/app/new/(content)/components/checkbox-group/demos/hero/tailwind/index.tsx
new file mode 100644
index 0000000000..15150b0658
--- /dev/null
+++ b/docs/src/app/new/(content)/components/checkbox-group/demos/hero/tailwind/index.tsx
@@ -0,0 +1,64 @@
+import * as React from 'react';
+import { Checkbox } from '@base-ui-components/react/checkbox';
+import { CheckboxGroup } from '@base-ui-components/react/checkbox-group';
+
+export default function ExampleCheckboxGroup() {
+ return (
+
+
+ Apples
+
+
+
+
+
+
+
+
+ Fuji
+
+
+
+
+
+
+
+
+ Gala
+
+
+
+
+
+
+
+
+ Granny Smith
+
+
+ );
+}
+
+function CheckIcon(props: React.ComponentProps<'svg'>) {
+ return (
+
+
+
+ );
+}
diff --git a/docs/src/app/new/(content)/components/checkbox-group/page.mdx b/docs/src/app/new/(content)/components/checkbox-group/page.mdx
new file mode 100644
index 0000000000..3c5b4e1097
--- /dev/null
+++ b/docs/src/app/new/(content)/components/checkbox-group/page.mdx
@@ -0,0 +1,21 @@
+# Checkbox Group
+
+A series of checkboxes with a shared state.
+
+
+
+
+## API reference
+
+Checkbox Group is meant to be composed together with Checkbox. Import the components and place them together:
+
+```jsx title="Anatomy"
+import { Checkbox } from '@base-ui-components/react/checkbox';
+import { CheckboxGroup } from '@base-ui-components/react/checkbox-group';
+
+
+
+ ;
+```
+
+
diff --git a/docs/src/app/new/(content)/components/checkbox/demos/hero/css-modules/index.module.css b/docs/src/app/new/(content)/components/checkbox/demos/hero/css-modules/index.module.css
new file mode 100644
index 0000000000..337c01a41a
--- /dev/null
+++ b/docs/src/app/new/(content)/components/checkbox/demos/hero/css-modules/index.module.css
@@ -0,0 +1,42 @@
+.Checkbox {
+ box-sizing: border-box;
+ display: flex;
+ width: 1.25rem;
+ height: 1.25rem;
+ align-items: center;
+ justify-content: center;
+ border-radius: 0.25rem;
+ outline: 0;
+ padding: 0;
+ margin: 0;
+ border: none;
+
+ &[data-checked] {
+ background-color: var(--color-gray-900);
+ }
+
+ &[data-unchecked] {
+ border: 1px solid var(--color-gray-300);
+ background-color: transparent;
+ }
+
+ &:focus-visible {
+ outline: 2px solid var(--color-blue);
+ outline-offset: 2px;
+ }
+}
+
+.Indicator {
+ width: 0.75rem;
+ height: 0.75rem;
+ color: var(--color-gray-50);
+
+ &[data-unchecked] {
+ display: none;
+ }
+}
+
+.Icon {
+ width: 100%;
+ height: 100%;
+}
diff --git a/docs/src/app/new/(content)/components/checkbox/demos/hero/css-modules/index.tsx b/docs/src/app/new/(content)/components/checkbox/demos/hero/css-modules/index.tsx
new file mode 100644
index 0000000000..be6c7bc9dd
--- /dev/null
+++ b/docs/src/app/new/(content)/components/checkbox/demos/hero/css-modules/index.tsx
@@ -0,0 +1,21 @@
+import * as React from 'react';
+import { Checkbox } from '@base-ui-components/react/checkbox';
+import styles from './index.module.css';
+
+export default function ExampleCheckbox() {
+ return (
+
+
+
+
+
+ );
+}
+
+function CheckIcon(props: React.ComponentProps<'svg'>) {
+ return (
+
+
+
+ );
+}
diff --git a/docs/src/app/new/(content)/components/checkbox/demos/hero/index.ts b/docs/src/app/new/(content)/components/checkbox/demos/hero/index.ts
new file mode 100644
index 0000000000..80097d6015
--- /dev/null
+++ b/docs/src/app/new/(content)/components/checkbox/demos/hero/index.ts
@@ -0,0 +1,3 @@
+'use client';
+export { default as CssModules } from './css-modules';
+export { default as Tailwind } from './tailwind';
diff --git a/docs/src/app/new/(content)/components/checkbox/demos/hero/tailwind/index.tsx b/docs/src/app/new/(content)/components/checkbox/demos/hero/tailwind/index.tsx
new file mode 100644
index 0000000000..e7aeca91f3
--- /dev/null
+++ b/docs/src/app/new/(content)/components/checkbox/demos/hero/tailwind/index.tsx
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import { Checkbox } from '@base-ui-components/react/checkbox';
+
+export default function ExampleCheckbox() {
+ return (
+
+
+
+
+
+ );
+}
+
+function CheckIcon(props: React.ComponentProps<'svg'>) {
+ return (
+
+
+
+ );
+}
diff --git a/docs/src/app/new/(content)/components/checkbox/page.mdx b/docs/src/app/new/(content)/components/checkbox/page.mdx
new file mode 100644
index 0000000000..3d367cb2f0
--- /dev/null
+++ b/docs/src/app/new/(content)/components/checkbox/page.mdx
@@ -0,0 +1,20 @@
+# Checkbox
+
+An easily stylable checkbox component.
+
+
+
+
+## API reference
+
+Import the component and place its parts the following way:
+
+```jsx title="Anatomy"
+import { Checkbox } from '@base-ui-components/react/checkbox';
+
+
+
+ ;
+```
+
+
diff --git a/docs/src/app/new/(content)/components/dialog/demos/hero/css-modules/index.module.css b/docs/src/app/new/(content)/components/dialog/demos/hero/css-modules/index.module.css
index 6e30e50b6d..bad9b0459f 100644
--- a/docs/src/app/new/(content)/components/dialog/demos/hero/css-modules/index.module.css
+++ b/docs/src/app/new/(content)/components/dialog/demos/hero/css-modules/index.module.css
@@ -1,5 +1,4 @@
-.Trigger,
-.Close {
+.Button {
box-sizing: border-box;
display: flex;
padding: 0.5rem 0.875rem;
@@ -22,19 +21,18 @@
background-color: var(--color-gray-100);
}
}
-}
-.Close {
- margin-left: auto;
+ &:active {
+ background-color: var(--color-gray-100);
+ }
}
.Backdrop {
- box-sizing: border-box;
position: fixed;
inset: 0;
background-color: black;
opacity: 0.2;
- transition: opacity 150ms ease-out;
+ transition: opacity 150ms cubic-bezier(0.45, 1.005, 0, 1.005);
@media (prefers-color-scheme: dark) {
opacity: 0.7;
@@ -52,8 +50,8 @@
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
- width: 320px;
- max-width: calc(100vw - 48px);
+ width: 24rem;
+ max-width: calc(100vw - 3rem);
margin-top: -2rem;
padding: 1.5rem;
border-radius: 0.5rem;
@@ -71,7 +69,6 @@
}
.Title {
- box-sizing: border-box;
margin-top: -0.375rem;
margin-bottom: 0.25rem;
font-size: 1.125rem;
@@ -81,7 +78,12 @@
}
.Description {
- box-sizing: border-box;
- margin: 0 0 1rem;
+ margin: 0 0 1.5rem;
color: var(--color-gray-600);
}
+
+.Actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 1rem;
+}
diff --git a/docs/src/app/new/(content)/components/dialog/demos/hero/css-modules/index.tsx b/docs/src/app/new/(content)/components/dialog/demos/hero/css-modules/index.tsx
index 77351605bb..6f9fdce841 100644
--- a/docs/src/app/new/(content)/components/dialog/demos/hero/css-modules/index.tsx
+++ b/docs/src/app/new/(content)/components/dialog/demos/hero/css-modules/index.tsx
@@ -12,7 +12,9 @@ export default function ExampleDialog() {
You are all caught up. Good job!
- Close
+
+ Close
+
);
diff --git a/docs/src/app/new/(content)/components/dialog/demos/hero/tailwind/index.tsx b/docs/src/app/new/(content)/components/dialog/demos/hero/tailwind/index.tsx
index 97878a16bf..8912b5c348 100644
--- a/docs/src/app/new/(content)/components/dialog/demos/hero/tailwind/index.tsx
+++ b/docs/src/app/new/(content)/components/dialog/demos/hero/tailwind/index.tsx
@@ -4,20 +4,22 @@ import { Dialog } from '@base-ui-components/react/dialog';
export default function ExampleDialog() {
return (
-
+
View notifications
-
-
+
+
Your notifications
-
+
You are all caught up. Good job!
-
- Close
-
+
+
+ Close
+
+
);
diff --git a/docs/src/app/new/(content)/components/dialog/page.mdx b/docs/src/app/new/(content)/components/dialog/page.mdx
index 96656ca9ab..7c770744d9 100644
--- a/docs/src/app/new/(content)/components/dialog/page.mdx
+++ b/docs/src/app/new/(content)/components/dialog/page.mdx
@@ -1,6 +1,7 @@
# Dialog
A popup that opens on top of the entire page.
+
diff --git a/docs/src/app/new/(content)/layout.css b/docs/src/app/new/(content)/layout.css
index ba23cd32bf..4f31f85220 100644
--- a/docs/src/app/new/(content)/layout.css
+++ b/docs/src/app/new/(content)/layout.css
@@ -10,7 +10,7 @@
grid-template-columns: 1fr;
@media (--sm) {
- padding-inline: 3rem;
+ padding-inline: 2.5rem;
}
@media (--show-side-nav) {
@@ -24,11 +24,13 @@
}
.ContentLayoutMain {
+ min-width: 0;
+ max-width: 48rem;
+ width: 100%;
+
padding-top: 1.5rem;
padding-bottom: 5rem;
- min-width: 0;
margin: 0 auto;
- width: 100%;
@media (--sm) {
padding-top: 2rem;
diff --git a/docs/src/app/new/(content)/layout.tsx b/docs/src/app/new/(content)/layout.tsx
index cd7084c8ab..dc3e077d7a 100644
--- a/docs/src/app/new/(content)/layout.tsx
+++ b/docs/src/app/new/(content)/layout.tsx
@@ -2,6 +2,7 @@ import * as React from 'react';
import type { Metadata } from 'next/types';
import * as SideNav from 'docs/src/components/SideNav';
import * as QuickNav from 'docs/src/components/quick-nav/QuickNav';
+import { nav } from 'docs/src/nav';
import './layout.css';
export default function Layout({ children }: React.PropsWithChildren) {
@@ -29,139 +30,6 @@ export default function Layout({ children }: React.PropsWithChildren) {
);
}
-const nav = [
- {
- label: 'Overview',
- links: [
- {
- label: 'Quick start',
- href: '/new/overview/quick-start',
- },
- {
- label: 'Accessibility',
- href: '/new/overview/accessibility',
- },
- {
- label: 'Releases',
- href: '/new/overview/releases',
- },
- {
- label: 'About',
- href: '/new/overview/about',
- },
- ],
- },
- {
- label: 'Handbook',
- links: [
- {
- label: 'Styling',
- href: '/new/handbook/styling',
- },
- {
- label: 'Animation',
- href: '/new/handbook/animation',
- },
- {
- label: 'Composition',
- href: '/new/handbook/composition',
- },
- {
- label: 'Migrating from Radix',
- href: '/new/handbook/migrating-from-radix',
- },
- ],
- },
- {
- label: 'Components',
- links: [
- {
- label: 'Alert Dialog',
- href: '/new/components/alert-dialog',
- },
- {
- label: 'Checkbox',
- href: '/new/components/checkbox',
- },
- {
- label: 'Checkbox Group',
- href: '/new/components/checkbox group',
- },
- {
- label: 'Collapsible',
- href: '/new/components/collapsible',
- },
- {
- label: 'Combobox',
- href: '/new/components/combobox',
- },
- {
- label: 'Datepicker',
- href: '/new/components/datepicker',
- },
- {
- label: 'Dialog',
- href: '/new/components/dialog',
- },
- {
- label: 'Field',
- href: '/new/components/field',
- },
- {
- label: 'Fieldset',
- href: '/new/components/fieldset',
- },
- {
- label: 'Form',
- href: '/new/components/form',
- },
- {
- label: 'Menu',
- href: '/new/components/menu',
- },
- {
- label: 'Number Field',
- href: '/new/components/number-field',
- },
- {
- label: 'Popover',
- href: '/new/components/popover',
- },
- {
- label: 'Preview Card',
- href: '/new/components/preview-card',
- },
- {
- label: 'Progress',
- href: '/new/components/progress',
- },
- {
- label: 'Radio Group',
- href: '/new/components/radio-group',
- },
- {
- label: 'Separator',
- href: '/new/components/separator',
- },
- {
- label: 'Slider',
- href: '/new/components/slider',
- },
- {
- label: 'Switch',
- href: '/new/components/switch',
- },
- {
- label: 'Tabs',
- href: '/new/components/tabs',
- },
- {
- label: 'Tooltip',
- href: '/new/components/tooltip',
- },
- ],
- },
-];
// Title and description are pulled from and in the MDX.
export const metadata: Metadata = {
title: null,
diff --git a/docs/src/app/new/layout.css b/docs/src/app/new/layout.css
index 7fc1e43fc3..a7356aa5bf 100644
--- a/docs/src/app/new/layout.css
+++ b/docs/src/app/new/layout.css
@@ -28,32 +28,28 @@
--root-layout-padding-x: 0rem;
padding-inline: var(--root-layout-padding-x);
- @media (--sm) {
- --root-layout-padding-x: 2rem;
- }
-
@media (--show-side-nav) {
--root-layout-padding-x: 3rem;
- }
- &::before,
- &::after {
- content: '';
- position: absolute;
- background-color: var(--color-gridline);
- height: 1px;
- right: 0;
- left: 0;
- }
+ &::before,
+ &::after {
+ content: '';
+ position: absolute;
+ background-color: var(--color-gridline);
+ height: 1px;
+ right: 0;
+ left: 0;
+ }
- &::before {
- top: 3rem;
- margin-top: -1px;
- }
+ &::before {
+ top: var(--header-height);
+ margin-top: -1px;
+ }
- &::after {
- bottom: 3rem;
- margin-bottom: -1px;
+ &::after {
+ bottom: var(--header-height);
+ margin-bottom: -1px;
+ }
}
}
@@ -64,9 +60,9 @@
min-height: 100dvh;
max-width: calc(var(--breakpoint-max-layout-width) - var(--root-layout-padding-x) * 2);
flex-direction: column;
- padding-bottom: 3rem;
+ padding-bottom: var(--header-height);
- @media (--sm) {
+ @media (--show-side-nav) {
&::before,
&::after {
content: '';
diff --git a/docs/src/components/Header.css b/docs/src/components/Header.css
index a95c3250f3..485a4d94a0 100644
--- a/docs/src/components/Header.css
+++ b/docs/src/components/Header.css
@@ -1,27 +1,43 @@
@layer components {
.Header {
@apply text-sm;
+ height: var(--header-height);
+ width: 100%;
+ }
+
+ .HeaderInner {
+ height: inherit;
display: flex;
align-items: center;
justify-content: space-between;
padding-inline: 1.5rem;
- height: 3rem;
+
+ position: fixed;
+ top: 0;
+ inset-inline: 0;
+ box-shadow: inset 0 -1px var(--color-gridline);
+ background-color: var(--color-gray-50);
+ z-index: 1;
+
+ @media (--show-side-nav) {
+ position: static;
+ box-shadow: none;
+ background-color: transparent;
+ }
}
- .HeaderLink {
+ .HeaderLink,
+ .HeaderButton {
display: flex;
align-items: center;
gap: 0.375rem;
- padding: 0.25rem;
- margin: -0.25rem;
+ padding: 0.25rem 0.5rem;
+ margin: -0.25rem -0.5rem;
border-radius: var(--radius-md);
- @media (hover: hover) {
- &:hover {
- text-decoration: underline;
- text-decoration-color: var(--color-gray-500);
- text-decoration-thickness: 1px;
- text-underline-offset: 2px;
+ @media not (hover: hover) {
+ &:active {
+ color: var(--color-gray-500);
}
}
@@ -35,4 +51,28 @@
flex-shrink: 0;
}
}
+
+ .HeaderButton {
+ @media (hover: hover) {
+ &:hover {
+ background-color: var(--color-gray-100);
+ }
+ }
+ @media not (hover: hover) {
+ &:active {
+ background-color: var(--color-gray-100);
+ }
+ }
+ }
+
+ .HeaderLink {
+ @media (hover: hover) {
+ &:hover {
+ text-decoration: underline;
+ text-decoration-color: var(--color-gray-500);
+ text-decoration-thickness: 1px;
+ text-underline-offset: 2px;
+ }
+ }
+ }
}
diff --git a/docs/src/components/Header.tsx b/docs/src/components/Header.tsx
index 191951bd44..4ad9a8d6c0 100644
--- a/docs/src/components/Header.tsx
+++ b/docs/src/components/Header.tsx
@@ -1,19 +1,82 @@
import * as React from 'react';
import NextLink from 'next/link';
-import { GitHubIcon } from '../icons/GitHub';
+import { GitHubIcon } from 'docs/src/icons/GitHub';
+import * as MobileNav from './MobileNav';
+import { nav } from '../nav';
+import { NpmIcon } from '../icons/Npm';
+
+const VERSION = 'v1.0.0-alpha-1';
+export const HEADER_HEIGHT = 48;
export function Header() {
return (
-
- Base UI
-
-
-
v1.0.0-alpha.1
-
-
- GitHub
-
+
+
+ base ui
+
+
+
+
+
+
+
+
+ Navigation
+
+
+
+ {nav.map((section) => (
+
+ {section.label}
+
+ {section.links.map((link) => (
+
+ {link.label}
+
+ ))}
+
+
+ ))}
+
+
+ Resources
+
+
+
+
+ npm package
+ {VERSION}
+
+
+
+
+ GitHub
+
+
+
+
+
+
);
diff --git a/docs/src/components/MobileNav.css b/docs/src/components/MobileNav.css
new file mode 100644
index 0000000000..1d7e65d95d
--- /dev/null
+++ b/docs/src/components/MobileNav.css
@@ -0,0 +1,249 @@
+@layer components {
+ .MobileNavBackdrop {
+ position: fixed;
+ inset: 0;
+ height: 100dvh;
+
+ transition-duration: 600ms;
+ transition-property: -webkit-backdrop-filter, backdrop-filter, opacity;
+ transition-timing-function: var(--ease-out-fast);
+ -webkit-backdrop-filter: blur(1.5px);
+ backdrop-filter: blur(1.5px);
+ background-image: linear-gradient(to bottom, transparent 2rem, rgb(0 0 0 / 5%) 50%);
+
+ @media (prefers-color-scheme: dark) {
+ background-image: linear-gradient(to bottom, transparent, rgb(0 0 0 / 25%) 6rem);
+ }
+
+ &[data-entering],
+ &[data-exiting] {
+ -webkit-backdrop-filter: blur(0px);
+ backdrop-filter: blur(0px);
+ opacity: 0;
+ }
+
+ &[data-exiting] {
+ transition-duration: 250ms;
+ transition-timing-function: var(--ease-in-slow);
+ }
+ }
+
+ .MobileNavPopup {
+ /* TODO Vlad remove when the backdrop order bug is fixed */
+ z-index: 1;
+ outline: 0;
+
+ position: fixed;
+ inset: 0;
+ height: 100dvh;
+
+ /* Half the transition duration for blur so the element sort of comes into the focus when it's halfway in */
+ transition-duration: 600ms, 300ms;
+ transform-origin: top center;
+ transition-property: transform, filter;
+ transition-timing-function: var(--ease-out-fast);
+
+ &[data-entering],
+ &[data-exiting] {
+ transform: translateY(100dvh);
+ filter: blur(1px);
+ }
+
+ &[data-exiting] {
+ /* Delay the blur transition so it doesn't feel unfocused until halway out */
+ transition-delay: 0ms, 125ms;
+ transition-duration: 250ms;
+ transition-timing-function: var(--ease-in-slow);
+ }
+ }
+
+ .MobileNavViewport {
+ --mobile-nav-item-height: 2.5rem;
+ --mobile-nav-item-padding-x: 1.5rem;
+
+ position: absolute;
+ inset: 0;
+ height: 100dvh;
+ overflow-y: auto;
+
+ @apply text-base;
+
+ /* Native apps don't show scrollbars on sheets like this */
+ scrollbar-width: none;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ /* A decorative border between the status bar and the page */
+ &::after {
+ content: '';
+ position: fixed;
+ z-index: 1;
+ top: 0;
+ inset-inline: 0;
+ height: 1px;
+ transition: background-color 50ms;
+ }
+ &[data-clipped]::after {
+ background-color: var(--color-gridline);
+ }
+ }
+
+ .MobileNavViewportInner {
+ position: relative;
+
+ /* Prevent children's margin collapse */
+ display: flex;
+ flex-direction: column;
+ }
+
+ .MobileNavBackdropTapArea {
+ position: absolute;
+ inset: 0;
+ }
+
+ .MobileNavPanel {
+ margin-top: var(--header-height);
+ position: relative;
+ padding-block: 1rem;
+
+ border-top-left-radius: var(--radius-xl);
+ border-top-right-radius: var(--radius-xl);
+
+ box-shadow:
+ 0 10px 64px -10px rgba(35, 39, 52, 0.2),
+ 0 0.25px 0 1px var(--color-gray-200);
+
+ @media (prefers-color-scheme: dark) {
+ box-shadow: 0 0 0 1px var(--color-gray-200);
+ }
+
+ /* Make bottom overscroll edges soft; visible during extreme rubber band overscroll at the bottom */
+ background-image: linear-gradient(to bottom, var(--color-popup) calc(100% - 2rem), transparent);
+
+ /* Make the panel narrower on wider screens and lose the overscroll gradient hocus pocus for a bottom margin */
+ @media (--sm) {
+ margin-block: 5rem;
+ margin-inline: auto;
+ width: calc(100% - 6rem);
+ max-width: 40rem;
+ background-image: none;
+ background-color: var(--color-popup);
+ border-radius: var(--radius-xl);
+ }
+ }
+
+ /* Smoothly hides bottom overscroll edge */
+ .MobileNavBottomOverscroll {
+ position: absolute;
+ inset: 0;
+ background-image: linear-gradient(to bottom, transparent 30%, var(--color-popup) 50%);
+
+ @media (--sm) {
+ display: none;
+ }
+ }
+
+ .MobileNavSection {
+ margin-bottom: 1rem;
+ }
+
+ .MobileNavHeading {
+ @apply text-lg;
+ font-weight: 500;
+ padding-inline: var(--mobile-nav-item-padding-x);
+
+ .MobileNavHeadingInner {
+ display: flex;
+ align-items: center;
+ height: var(--mobile-nav-item-height);
+ }
+
+ &::after {
+ content: '';
+ display: block;
+ margin-top: -1px;
+ border-bottom: 1px solid var(--color-gray-200);
+ }
+ }
+
+ /* TODO Vlad should headings be just items? */
+ .MobileNavItem {
+ /*
+ * Border is a separate element so that links
+ * span the entire screen width and have the
+ * tap-highlight-color applied edge to edge.
+ */
+ &::after {
+ content: '';
+ display: block;
+ margin-inline: var(--mobile-nav-item-padding-x);
+ border-bottom: 1px solid var(--color-gray-200);
+ }
+ }
+
+ .MobileNavLink {
+ flex-grow: 1;
+ display: flex;
+ gap: 0.675rem;
+ align-items: center;
+ height: var(--mobile-nav-item-height);
+ padding-inline: var(--mobile-nav-item-padding-x);
+
+ @supports not (-webkit-tap-highlight-color: black) {
+ &:active {
+ background-color: var(--color-gray-100);
+ }
+ }
+ @supports (-webkit-tap-highlight-color: black) {
+ /* Applying background-color on :active shows it when touching items while scrolling */
+ /* This activates only on real link taps */
+ -webkit-tap-highlight-color: var(--color-gray-300);
+ }
+
+ &:focus-visible {
+ outline: 2px solid var(--color-blue);
+ outline-offset: -1px;
+ }
+ }
+}
+
+.MobileNavCloseContainer {
+ position: sticky;
+ top: 0.75rem;
+ left: 100%;
+ margin-right: 0.75rem;
+ height: 0;
+ width: fit-content;
+}
+
+.MobileNavClose {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--color-gray-500);
+ outline: 0;
+
+ /* Visible circle */
+ width: 1.75rem;
+ height: 1.75rem;
+ border-radius: 100%;
+ background-color: var(--color-gray-200);
+
+ /* Blur the dividers behind */
+ -webkit-backdrop-filter: blur(1rem);
+ backdrop-filter: blur(1rem);
+
+ &:focus-visible {
+ outline: 2px solid var(--color-blue);
+ outline-offset: -1px;
+ }
+
+ /* Tap target */
+ &::after {
+ content: '';
+ width: 3rem;
+ height: 3rem;
+ position: absolute;
+ }
+}
diff --git a/docs/src/components/MobileNav.tsx b/docs/src/components/MobileNav.tsx
new file mode 100644
index 0000000000..718de77f3b
--- /dev/null
+++ b/docs/src/components/MobileNav.tsx
@@ -0,0 +1,153 @@
+'use client';
+import * as React from 'react';
+import clsx from 'clsx';
+import NextLink from 'next/link';
+import { Dialog } from '@base-ui-components/react/dialog';
+import { HEADER_HEIGHT } from './Header';
+
+type MobileNavState = [boolean, (open: boolean) => void];
+const MobileNavState = React.createContext
([false, () => undefined]);
+
+export function Root(props: Dialog.Root.Props) {
+ const state = React.useState(false);
+ const [open, setOpen] = state;
+
+ return (
+
+
+
+ );
+}
+
+export const Trigger = Dialog.Trigger;
+
+export function Backdrop({ className, ...props }: Dialog.Backdrop.Props) {
+ return ;
+}
+
+export function Popup({ children, className, ...props }: Dialog.Popup.Props) {
+ const [, setOpen] = React.useContext(MobileNavState);
+ const rem = React.useRef(16);
+
+ React.useEffect(() => {
+ rem.current = parseFloat(getComputedStyle(document.documentElement).fontSize);
+ }, []);
+
+ return (
+
+
+ {
+ const viewport = event.currentTarget;
+ if (viewport.scrollTop > (HEADER_HEIGHT * rem.current) / 16) {
+ viewport.setAttribute('data-clipped', '');
+ } else {
+ viewport.removeAttribute('data-clipped');
+ }
+ }}
+ onTouchStart={(event) => {
+ const viewport = event.currentTarget;
+
+ // Consider flicks from scroll top only (iOS does the same with its sheets)
+ if (viewport.scrollTop <= 0) {
+ viewport.addEventListener(
+ 'touchend',
+ function handleTouchEnd() {
+ // If touch ended and we are overscrolling past a threshold...
+ if (viewport.scrollTop < -32) {
+ const y = viewport.scrollTop;
+ viewport.addEventListener(
+ 'scroll',
+ function handleNextScroll() {
+ // ...look at whether the system's intertia scrolling is continuing the motion
+ // in the same direction. If so, the flick is strong enough to close the dialog.
+ if (viewport.scrollTop < y) {
+ // It's gonna eventually bounce back to scrollTop 0. We need to counteract this
+ // a bit so that the close transition doesn't appear slower than it should.
+ viewport.style.translate = `0px -${y}px`;
+ viewport.style.transform = `400ms`;
+ setOpen(false);
+
+ // Sometimes the first scroll event comes with the same scroll position
+ // If so, give it another chance, call ourselves recursively
+ } else if (viewport.scrollTop === y) {
+ viewport.addEventListener('scroll', handleNextScroll, { once: true });
+ }
+ },
+ { once: true },
+ );
+ }
+ },
+ { once: true },
+ );
+ }
+ }}
+ >
+
+ {/* We need the area behind the panel to close on tap but also to scroll the viewport. */}
+
} />
+
+
+ {/* Reverse order to place the close button at the end of the DOM, but at sticky top visually */}
+
+
+
+
+
+ );
+}
+
+export function Section({ className, ...props }: React.ComponentProps<'div'>) {
+ return
;
+}
+
+export function Heading({ children, className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+export function List({ className, ...props }: React.ComponentProps<'ul'>) {
+ return ;
+}
+
+interface ItemProps extends React.ComponentPropsWithoutRef<'li'> {
+ active?: boolean;
+ href: string;
+ rel?: string;
+}
+
+export function Item({ children, className, href, rel, ...props }: ItemProps) {
+ const [, setOpen] = React.useContext(MobileNavState);
+ return (
+
+ setOpen(false)}>
+ {children}
+
+
+ );
+}
diff --git a/docs/src/components/Popup.css b/docs/src/components/Popup.css
index 306a16b1f3..ca300121e5 100644
--- a/docs/src/components/Popup.css
+++ b/docs/src/components/Popup.css
@@ -1,7 +1,7 @@
@layer components {
.Popup {
- max-width: var(--available-width);
- max-height: var(--available-height);
+ max-width: var(--available-width, none);
+ max-height: var(--available-height, none);
border-radius: var(--radius-md);
background-color: var(--color-popup);
overflow: hidden;
@@ -18,10 +18,10 @@
outline-offset: -1px;
}
- transform-origin: var(--transform-origin);
- transition-duration: 150ms;
+ transform-origin: var(--transform-origin, center);
+ transition-duration: 120ms;
transition-property: opacity, transform;
- transition-timing-function: cubic-bezier(0.3, 1.065, 0.01, 0.975);
+ transition-timing-function: var(--ease-out-fast);
&[data-entering],
&[data-exiting] {
@@ -32,7 +32,7 @@
}
&[data-exiting] {
- transition-duration: 275ms;
+ transition-timing-function: var(--ease-in-slow);
}
}
}
diff --git a/docs/src/components/SideNav.css b/docs/src/components/SideNav.css
index ea5c1dac65..f655b2bc77 100644
--- a/docs/src/components/SideNav.css
+++ b/docs/src/components/SideNav.css
@@ -1,5 +1,6 @@
@layer components {
.SideNavRoot {
+ /* Match quick nav spacing so side nav and quick nav are visually aligned */
--side-nav-item-height: 2rem;
--side-nav-item-line-height: var(--text-sm--line-height);
--side-nav-item-padding-y: calc(
diff --git a/docs/src/components/SideNav.tsx b/docs/src/components/SideNav.tsx
index 0f948a2bf0..c9f322bc3c 100644
--- a/docs/src/components/SideNav.tsx
+++ b/docs/src/components/SideNav.tsx
@@ -7,6 +7,7 @@ import { ScrollArea } from '@base-ui-components/react/scroll-area';
import scrollIntoView from 'scroll-into-view-if-needed';
// eslint-disable-next-line no-restricted-imports
import { SCROLL_TIMEOUT } from '@base-ui-components/react/scroll-area/constants';
+import { HEADER_HEIGHT } from './Header';
interface SideNavContextValue {
/**
@@ -74,17 +75,23 @@ interface ItemProps extends React.ComponentProps<'li'> {
href: string;
}
+const SCROLL_MARGIN = 48;
+
export function Item({ children, className, href, ...props }: ItemProps) {
const { setScrollingIntoView } = React.useContext(SideNavContext);
const ref = React.useRef(null);
const pathname = usePathname();
const active = pathname === href;
+ const rem = React.useRef(16);
+
+ React.useEffect(() => {
+ rem.current = parseFloat(getComputedStyle(document.documentElement).fontSize);
+ }, []);
React.useEffect(() => {
if (ref.current && active) {
- // TODO Vlad this should be rem, not 48px
- const HEADER_HEIGHT = 48;
- const SCROLL_MARGIN = 48;
+ const scrollMargin = (SCROLL_MARGIN * rem.current) / 16;
+ const headerHeight = (HEADER_HEIGHT * rem.current) / 16;
const viewport = document.querySelector('[data-side-nav-viewport]');
if (!viewport) {
@@ -104,8 +111,8 @@ export function Item({ children, className, href, ...props }: ItemProps) {
}
actions.forEach(({ top }) => {
const dir = viewport.scrollTop > top ? -1 : 1;
- const offset = Math.max(0, HEADER_HEIGHT - Math.max(0, window.scrollY));
- viewport.scrollTop = top + offset + SCROLL_MARGIN * dir;
+ const offset = Math.max(0, headerHeight - Math.max(0, window.scrollY));
+ viewport.scrollTop = top + offset + scrollMargin * dir;
});
},
});
diff --git a/docs/src/components/quick-nav/QuickNav.css b/docs/src/components/quick-nav/QuickNav.css
index 8f75b634e2..9a635512ea 100644
--- a/docs/src/components/quick-nav/QuickNav.css
+++ b/docs/src/components/quick-nav/QuickNav.css
@@ -7,6 +7,8 @@
.QuickNavRoot {
--quick-nav-margin-x: 2rem;
+
+ /* Use line height instead of fixed item height in case text breaks into multiple lines */
--quick-nav-item-height: 2rem;
--quick-nav-item-line-height: var(--text-sm--line-height);
--quick-nav-item-padding-y: calc(
diff --git a/docs/src/components/reference/ReferenceTablePopover.tsx b/docs/src/components/reference/ReferenceTablePopover.tsx
index 8ef7db3625..97c1750ad5 100644
--- a/docs/src/components/reference/ReferenceTablePopover.tsx
+++ b/docs/src/components/reference/ReferenceTablePopover.tsx
@@ -8,7 +8,7 @@ import { Popup } from '../Popup';
export function ReferenceTablePopover({ children }: React.PropsWithChildren) {
const isMobile = useMediaQuery('@media (width < 48rem)', { noSsr: true });
return (
-
+
diff --git a/docs/src/icons/Npm.tsx b/docs/src/icons/Npm.tsx
new file mode 100644
index 0000000000..fac114950c
--- /dev/null
+++ b/docs/src/icons/Npm.tsx
@@ -0,0 +1,16 @@
+import * as React from 'react';
+
+export function NpmIcon(props: React.ComponentProps<'svg'>) {
+ return (
+
+
+
+ );
+}
diff --git a/docs/src/mdx-components.tsx b/docs/src/mdx-components.tsx
index b1161ea2e3..075d76c342 100644
--- a/docs/src/mdx-components.tsx
+++ b/docs/src/mdx-components.tsx
@@ -18,20 +18,22 @@ export const mdxComponents: MDXComponents = {
code: (props) =>
,
h1: (props) => (
-
+
{`${getChildrenText(props.children)} · Base UI`}
),
h2: (props) => (
),
- h3: (props) => ,
- h4: (props) => ,
- h5: (props) => ,
- h6: (props) => ,
+ h3: (props) => (
+
+ ),
+ h4: (props) => ,
+ h5: (props) => ,
+ h6: (props) => ,
p: (props) =>
,
figure: (props) => {
if ('data-rehype-pretty-code-figure' in props) {
@@ -63,13 +65,14 @@ export const mdxComponents: MDXComponents = {
QuickNav,
AttributesTable: (props) => ,
CssVariablesTable: (props) => ,
+ Meta: (props: React.ComponentProps<'meta'>) => {
+ if (props.name === 'description' && String(props.content).length > 170) {
+ throw new Error('Meta description shouldn’t be longer than 170 chars');
+ }
+ return ;
+ },
PropsTable: (props) => ,
- Subtitle: (props) => (
-
-
-
-
- ),
+ Subtitle: (props) =>
,
};
export const inlineMdxComponents: MDXComponents = {
diff --git a/docs/src/nav.ts b/docs/src/nav.ts
new file mode 100644
index 0000000000..7e815feae9
--- /dev/null
+++ b/docs/src/nav.ts
@@ -0,0 +1,125 @@
+export const nav = [
+ {
+ label: 'Overview',
+ links: [
+ {
+ label: 'Quick start',
+ href: '/new/overview/quick-start',
+ },
+ {
+ label: 'Accessibility',
+ href: '/new/overview/accessibility',
+ },
+ {
+ label: 'Releases',
+ href: '/new/overview/releases',
+ },
+ {
+ label: 'About',
+ href: '/new/overview/about',
+ },
+ ],
+ },
+ {
+ label: 'Handbook',
+ links: [
+ {
+ label: 'Styling',
+ href: '/new/handbook/styling',
+ },
+ {
+ label: 'Animation',
+ href: '/new/handbook/animation',
+ },
+ {
+ label: 'Composition',
+ href: '/new/handbook/composition',
+ },
+ ],
+ },
+ {
+ label: 'Components',
+ links: [
+ {
+ label: 'Accordion',
+ href: '/new/components/accordion',
+ },
+ {
+ label: 'Alert Dialog',
+ href: '/new/components/alert-dialog',
+ },
+ {
+ label: 'Checkbox',
+ href: '/new/components/checkbox',
+ },
+ {
+ label: 'Checkbox Group',
+ href: '/new/components/checkbox-group',
+ },
+ {
+ label: 'Collapsible',
+ href: '/new/components/collapsible',
+ },
+ {
+ label: 'Dialog',
+ href: '/new/components/dialog',
+ },
+ {
+ label: 'Field',
+ href: '/new/components/field',
+ },
+ {
+ label: 'Fieldset',
+ href: '/new/components/fieldset',
+ },
+ {
+ label: 'Form',
+ href: '/new/components/form',
+ },
+ {
+ label: 'Menu',
+ href: '/new/components/menu',
+ },
+ {
+ label: 'Number Field',
+ href: '/new/components/number-field',
+ },
+ {
+ label: 'Popover',
+ href: '/new/components/popover',
+ },
+ {
+ label: 'Preview Card',
+ href: '/new/components/preview-card',
+ },
+ {
+ label: 'Progress',
+ href: '/new/components/progress',
+ },
+ {
+ label: 'Radio Group',
+ href: '/new/components/radio-group',
+ },
+ {
+ label: 'Separator',
+ href: '/new/components/separator',
+ },
+ {
+ label: 'Slider',
+ href: '/new/components/slider',
+ },
+ {
+ label: 'Switch',
+ href: '/new/components/switch',
+ },
+ {
+ label: 'Tabs',
+ href: '/new/components/tabs',
+ },
+ {
+ label: 'Tooltip',
+ href: '/new/components/tooltip',
+ },
+ ],
+ },
+];
diff --git a/docs/src/styles.css b/docs/src/styles.css
index b854fc8c12..e6cb7c6b6e 100644
--- a/docs/src/styles.css
+++ b/docs/src/styles.css
@@ -15,6 +15,7 @@
@import './components/Code.css';
@import './components/CodeBlock.css';
@import './components/GhostButton.css';
+@import './components/MobileNav.css';
@import './components/Header.css';
@import './components/Popup.css';
@import './components/Select.css';
@@ -33,7 +34,7 @@
--color-gray-50: oklch(98% 0.25% 264);
--color-gray-75: oklch(97% 0.325% 264); /* Not a Tailwind step, don't use it in the demos */
--color-gray-100: oklch(12% 9.5% 264 / 5%);
- --color-gray-200: oklch(12% 9% 264 / 8%);
+ --color-gray-200: oklch(12% 9% 264 / 7%);
--color-gray-300: oklch(12% 8.5% 264 / 17%);
--color-gray-400: oklch(12% 8% 264 / 38%);
--color-gray-500: oklch(12% 7.5% 264 / 50%);
@@ -104,6 +105,11 @@
--text-6xl: 3.75rem;
--text-6xl--line-height: 0.95;
--text-6xl--letter-spacing: -0.015em;
+
+ --default-transition-timing-function: var(--ease-out-fast);
+ --ease-out-fast: cubic-bezier(0.45, 1.005, 0, 1.005);
+ --ease-in-slow: cubic-bezier(0.375, 0.015, 0.545, 0.455);
+ --header-height: 3rem;
}
:root {
@@ -111,7 +117,7 @@
--color-gray-50: oklch(17% 1% 264);
--color-gray-75: oklch(19% 2% 264); /* Not a Tailwind step, don't use it in the demos */
--color-gray-100: oklch(28% 3% 264 / 65%);
- --color-gray-200: oklch(31% 3% 264 / 80%);
+ --color-gray-200: oklch(29% 3% 264 / 80%);
--color-gray-300: oklch(35% 3% 264 / 80%);
--color-gray-400: oklch(47% 3.5% 264 / 80%);
--color-gray-500: oklch(64% 4% 264 / 80%);