# Admin table (/docs/blocks/admin-table)
The screen behind "Settings → Team" in most SaaS apps, inside the app's shell: header, sidebar (an icon rail on tablets, a menu drawer on phones) and the content area. Everything works: filter by role, team and status, sort any column, select rows across pages, change roles in bulk, edit someone in a drawer, invite a new member and delete with a confirmation. Changes are kept in local state until you reload the page.
## Components [#components]
## Behavior [#behavior]
- **Search and filters.** The search box matches names and emails as you type. Role and status are `Select`s with an "All" option; team is a `Combobox`, so you can type to find it in a longer list. On a phone, the filters move into a bottom drawer behind a Filters button that counts the active ones. Clear filters appears when any filter is on, and the line above the table says how many members match.
- **Sorting.** Every column with data sorts on click: ascending, descending, then back to unsorted. The table starts sorted by name.
- **Selection and bulk actions.** Rows have checkboxes, and the header one selects every row that matches the filters, on every page. With a selection, a bar offers Change role, Suspend and Delete. Changing a filter clears the selection, so a bulk action never reaches rows you can't see.
- **Pagination.** Ten rows per page. A new filter goes back to page 1.
- **Row menu.** Edit, Suspend or Reactivate, and Delete for one member.
- **Edit and invite in a drawer.** The same form edits a member or invites a new one. It checks the name, the email format and that no one else uses the email.
- **Delete with a confirmation.** An `AlertDialog` names who is being deleted and what happens next. Every change ends with a toast.
- **Narrow screens.** Below 720px the Team and Last active columns are dropped; on a phone, role and status move under the name. If it still doesn't fit, the table scrolls sideways.
## Accessibility [#accessibility]
- The table has a name, and sortable headers are buttons that set `aria-sort`, so screen readers say which column is sorted and in which order.
- Every checkbox and row menu is named after its member ("Select Aline Prado", "Actions for Aline Prado"); the selection count and the "Showing 12 of 18" line are live regions.
- The drawer and the dialog trap focus and return it to the menu that opened them. After a delete, the row is gone, so focus moves to the page heading instead.
- When the drawer form fails, focus moves to the first field with an error, and the error is read with it.
## Use the code [#use-the-code]
The Code tab shows the whole screen as one file, with `@camaleo/react` and `lucide-react` as its only dependencies (`DataTable` brings TanStack Table with it). The members are fixed data: replace them with your API, and move the filtering and paging to the server when the list gets long (`DataTable` takes `pageCount`, `totalRows` and controlled `sorting` for that).
# Sign in (/docs/blocks/auth)
The account screens most products need on day one, in one file. Sign in and Create account are tabs; Forgot password, the one-time code after sign-up, the "check your email" step and the signed-in state are views of the same card. On wide screens a brand panel with a customer quote sits beside the form; on narrower ones the form stands alone. Everything validates, and the test code for the verification step is shown under the field.
## Components [#components]
## Behavior [#behavior]
- **Sign in.** Email and password are required; the password has a show and hide button. "Keep me signed in" is a plain checkbox you can wire to your session length.
- **Create account.** Name, email, a password with three rules that tick as you type (8 characters, a letter and a number, not your email) and the terms checkbox. Submitting goes to the code step.
- **One-time code.** Six cells that take typing, Backspace and a pasted code. A short code and a wrong code get different errors. "Send a new code" waits 30 seconds between sends and says so on the button.
- **Forgot password.** It asks for the email and then answers the same way whether or not the account exists, so the form can't be used to find out who has an account.
- **Magic link and passkey.** The two buttons under "or" stand in for passwordless sign-in. They use generic icons on purpose: if you add Google or Apple sign-in, follow each provider's own button guidelines.
## Accessibility [#accessibility]
- Every step change moves focus to the card heading, so screen reader users hear "Check your email" or "Reset your password" instead of silence. Switching tabs leaves focus on the tab, as the tabs pattern expects.
- A failed submit moves focus to the first field with an error; the error is linked to the field and read with it.
- The password rules are said once in the field's description. The ticking checklist under it is visual only (`aria-hidden`), so it doesn't repeat itself on every keystroke.
- The show-password button changes its name ("Show password", "Hide password") instead of using `aria-pressed`. Each code cell after the first is named "Digit 2 of 6" and so on.
- The resend button uses `aria-disabled` while it counts down, so it keeps focus and stays in the Tab order.
- `autocomplete` is set on every field (`username`, `current-password`, `new-password`, `one-time-code`), so password managers and SMS code autofill work.
## Use the code [#use-the-code]
The Code tab shows the whole screen as one file, with `@camaleo/react` and `lucide-react` as its only dependencies. The `wait` function stands in for your auth server: replace it with your requests, and replace `DEMO_CODE` with a check on the server. Never decide on the client whether a code or a password is right.
# Checkout (/docs/blocks/checkout)
A one-page checkout that keeps the order summary in view while people move through shipping address, delivery, payment and review. Fill it in, break it on purpose and press Continue: every step checks its own fields before letting you move on. The card field is a demo; nothing is sent anywhere.
## Components [#components]
## Behavior [#behavior]
- **Validation per step.** Continue checks only the fields of the current step. The errors appear after the first failed attempt and then update as you type, so fixing a field clears its message without another click.
- **Postal code by country.** The format hint and the check follow the country you pick: `01310-100` for Brazil, `94103` for the United States, `1100-148` for Portugal, `M5V 2T6` for Canada.
- **Card fields format as you type.** The number is grouped in fours and checked with the Luhn checksum, the expiry gets its slash and must not have passed, and the security code takes 3 or 4 digits. `autocomplete` tokens (`cc-number`, `cc-exp`, `cc-csc`) let browsers and password managers fill them.
- **Delivery changes the total.** The radio cards are a real `RadioGroup`: the summary and the Pay button update with the shipping price.
- **Promo code.** `CAMALEO10` takes 10% off the items; any other code shows an error under the field.
- **Review and edit.** The review step lists what you entered, with an Edit button that jumps back to that step. Pay shows a loading state, then the confirmation replaces the form.
## Accessibility [#accessibility]
- After a failed Continue, focus moves to the `ErrorSummary` at the top of the step; each link in it moves focus to its field.
- When the step changes, focus moves to the step heading, which reads "Step 2 of 4: Delivery". The `Stepper` marks the current step with `aria-current="step"`, and finished steps say "done" to screen readers.
- Every field has a visible label, and hints and errors are linked to it, so they are read when the field gets focus.
- The confirmation heading takes focus when it replaces the form, so keyboard and screen reader users don't lose their place.
## Use the code [#use-the-code]
The Code tab shows the whole screen as one file. It needs `@camaleo/react` and `lucide-react`, and nothing else: the validation is a plain function you can swap for your schema library. Replace the fixed cart and prices with your data, and send the payment fields to your provider's own components (Stripe Elements, Adyen, Pagar.me) instead of your server: card numbers should never touch it.
# Sales dashboard (/docs/blocks/dashboard)
## Built with [#built-with]
## Behavior [#behavior]
- **Three layouts by width.** The shell measures itself: a full sidebar from 1000px, a rail of icons from 640px, and below that a menu button that opens the same navigation in a `Drawer`. Try the Tablet and Phone widths above. In your app, a media query on the window does the same job.
- **Period filter.** 7, 30 or 90 days changes the stats, their deltas and the chart; 90 days switches the chart to weekly bars.
- **No chart library.** The bars are plain elements sized in percent and colored with `--cml-accent` and `--cml-brand-300`, so the chart follows the skin and the brand color. Grid lines mix `--cml-fg` at low opacity, so they stay faint even in skins whose border is solid black. Swap in your chart library when you need axes and tooltips.
- **Orders.** The channel filter narrows the `DataTable`; the table sorts by any column, pages by 5 and shows an empty state when a channel has no orders.
- **Good and bad news.** For the refund rate, going down is good: `StatDelta` gets `sentiment="positive"` with `trend="down"`, so the color matches the meaning.
## Accessibility [#accessibility]
- The header, sidebar and content are a `header`, an `aside` with a labelled `nav`, and the page's `main`. On this page the content renders a `div`, because the docs already have a `main`.
- The chart's bars are hidden from screen readers; the card description gives the best day and its revenue in words.
- Period buttons are a `ToggleGroup` with full names ("Last 30 days") as their accessible names, not just "30d".
- Sidebar items in the rail keep their names (and a `title` tooltip), and the current page has `aria-current="page"`.
# Inbox (/docs/blocks/inbox)
A three-pane inbox: folders, the conversation list and the message. It follows its own width, not the window's: at tablet width the folders move into a select above the list, and on a phone the list and the message take turns on screen. Try it at each width with the toggle above the preview.
## Components [#components]
## Behavior [#behavior]
- **Unread state.** Unread conversations have a bold sender and subject and a bar in the accent color. Opening one marks it as read; the folder counts update.
- **Selection.** Each row has a checkbox, and the one above the list selects every visible row (it shows a dash when only some are). The toolbar acts on the selection: Archive, Delete and Mark as read or unread. In Archive and Trash, Archive becomes Move to Inbox.
- **Undo.** Moving conversations shows a toast with an Undo button that puts them back where they were.
- **Search.** The search box filters the current folder by sender, subject and preview as you type, ignoring accents ("julia" finds "Júlia"). When nothing matches, an empty state says so and offers to clear the search.
- **Reading pane.** With room for both panes, the first conversation is open from the start, and archiving or deleting it opens the next one. The message shows its sender, date and body, with the same actions in a toolbar and a reply box that checks it isn't empty before sending.
- **Compose.** Compose (in the folder column, or above the list on narrower screens) opens a new message in the reading pane; it checks the recipient's email and that the message isn't empty.
- **Empty folders.** An empty Inbox says you're caught up; Archive and Trash say what ends up there.
## Accessibility [#accessibility]
- The list uses a roving tab stop: Tab enters the list once, on the last row you were on. ↑ and ↓ move between rows, Home and End jump to the ends, X selects the focused row and Enter opens it. With a keyboard and room to spare, the keys are listed under the list and linked to it with `aria-describedby`; on touch screens and phones the hint is hidden. The focused row gets the skin's focus ring.
- Unread rows start with a hidden "unread" so screen readers hear the state, not just the bold text. The open conversation has `aria-current`.
- Each checkbox is named after its conversation ("Select Marina Costa: Brand guidelines, version 3"), and the icon buttons in the toolbars have names and tooltips.
- When rows disappear (archived or deleted), focus moves to the folder heading instead of being lost. On a phone, opening a message moves focus to its subject, and Back returns it to the row.
- The selection count and the toasts are announced through live regions.
## Use the code [#use-the-code]
The Code tab shows the whole screen as one file, with `@camaleo/react` and `lucide-react` as its only dependencies. Messages are fixed data and the folder of each one lives in local state; swap both for your API. The `useWidth` hook at the top is what lets the layout follow its own box, so it also works inside a resizable panel.
# Examples (/docs/blocks)
## Marketing [#marketing]
## App [#app]
## Commerce [#commerce]
## Account [#account]
# Product landing (/docs/blocks/landing)
## Built with [#built-with]
## Behavior [#behavior]
- **Header.** From 760px the links sit in the header; below that they move into a `Menu` behind a button, and only the main call to action stays visible.
- **Hero.** The product glimpse on the right is made of the same `Stat`, `Progress` and `Badge` you'd use in the app, so it changes with the skin. It is decorative and hidden from screen readers.
- **Color from tokens.** The washes behind the hero, the icons and the closing band mix `--cml-accent` with transparency, so a new brand color repaints the page.
- **Sizes that follow the width.** Headings and spacing use `clamp()` with `cqi` units, and every grid is `auto-fit`, so the page needs no breakpoints. Outside a size container, `cqi` falls back to the viewport.
- **Sign-up.** The closing form checks the email on submit, shows the error under the field and replaces itself with a confirmation.
## Accessibility [#accessibility]
- One `h1` in the hero, an `h2` per section (the testimonial's is visually hidden) and `h3` for cards, FAQ questions and footer columns.
- The header and footer navigations have distinct names, and in-page links point at the sections by `id`.
- The rating is one image named "Rated 5 of 5", not five separate stars.
- The FAQ uses `Accordion`, so each question is a button inside a heading with `aria-expanded`.
# Pricing (/docs/blocks/pricing)
## Built with [#built-with]
## Behavior [#behavior]
- **Monthly or yearly.** The billing switch is a `ToggleGroup` holding one value. Pressing the active option again keeps it, so the page never shows no price. Yearly prices show the per-month amount and the yearly total under it.
- **One highlighted plan.** The popular plan gets an accent outline, a solid button and a badge; the other two use outline buttons, so there is one obvious next step.
- **Comparison table.** Rows are features and columns are plans. Under 560px a plan switcher shows one plan next to the features, so no column hides off-screen.
- **Layout.** The page measures its own width: three plans side by side from 700px, one per row below. Never two plus an orphan, which reads like a missing plan.
## Accessibility [#accessibility]
- A visually hidden live region says "Showing yearly prices" when the switch changes, because the prices change somewhere else on the page.
- Each plan button reads its plan ("Start a 14-day trial, Team"), so a list of buttons still makes sense.
- Check and dash icons in the table are hidden from screen readers and carry the text "Included" or "Not included"; the feature names are row headers.
- The FAQ triggers are `h3` headings under the "Questions about billing" `h2`, set with `AccordionTrigger level`.
# Account settings (/docs/blocks/settings)
## Built with [#built-with]
## Behavior [#behavior]
- **Inside the app.** Settings live in the same `AppShell` as the sales dashboard: a sidebar from 1000px, a rail of icons from 640px, a `Drawer` below. Sections are tabs; under 640px a select picks the section, so no tab hides off-screen. Try the Phone width above.
- **Profile.** Upload photo opens the file picker and shows the image in the `Avatar` at once (an object URL; send the file to your server on Save). The bio counts down its 160 characters. Save validates name and email and confirms in a status line.
- **Notifications.** Each switch is a whole-row label, so the title and hint toggle it too. They save as you switch, which the footer says.
- **Security.** The password form checks the current password, the new password's rules and that the confirmation matches. Turning on two-factor shows the setup key and an `OtpField`; Verify needs all 6 digits, then the badge turns to On.
- **Danger zone.** Delete account opens an `AlertDialog` whose delete button stays disabled until you type `marina`. Confirming swaps the card for a warning with an undo.
## Accessibility [#accessibility]
- Arrow keys move between tabs and Enter opens one. Each section starts with a visually hidden `h2` (the tab or the select already shows its name), and the cards inside use `h3`.
- Saved and error messages are in `role="status"` regions or tied to their fields with `FieldError`, so screen readers hear them.
- The OTP cells are named "Digit 2 of 6", and the first takes the field's label.
- The `AlertDialog` traps focus, starts on its first control and returns focus to the trigger when it closes.
# Multi-step signup (/docs/blocks/signup)
## Built with [#built-with]
## Behavior [#behavior]
- **Brand panel.** From 860px a panel in the brand color sits beside the form, with the reasons to sign up and a customer quote; narrower, the form stands alone with the logo above it.
- **Validation per step.** Continue checks only the current step. Errors appear after the first Continue on that step, then update as you type, so a fixed field stops being red at once.
- **Error summary.** A failed Continue shows an `ErrorSummary` with one link per problem; each link moves focus to its field. The step turns red in the Stepper until it is fixed.
- **Address from the name.** The workspace address follows the name ("Café Serra Azul" → `cafe-serra-azul`) until you edit it yourself.
- **Review.** The last step lists everything entered, with an Edit button per section that jumps back to it. Values are kept when you move between steps.
- **Success.** Creating the workspace replaces the form with a confirmation that repeats the email address.
## Accessibility [#accessibility]
- Moving to a new step puts focus on its heading, and a failed Continue puts it on the error summary, so a screen reader starts in the right place.
- The Stepper is an ordered list; the current step has `aria-current="step"` and finished steps say "completed".
- Every error is also in the field's `FieldError`, tied to the input, and the summary title says how many problems there are.
- The form uses `noValidate`, so the browser's own bubbles don't compete with the summary; `autoComplete` values let password managers fill the account step.
# Changelog (/docs/changelog)
## 1.0 — Unreleased [#10--unreleased]
1.0 will be the first version on npm. It is being finished now: the documentation site (this one), a last pass on the library and the release setup. Until then, the packages are at `0.0.0` and the entries below are the history of the repository.
## Skin polish [#skin-polish]
30 Oct 2026 · styles
- `material`: vertical tabs get a side indicator instead of the bottom underline.
- `glass`: the lines between future steps of a `Stepper` stay visible in light mode.
- `neubrutalism`: text placed directly in an `Alert` keeps 4.5:1.
- `progressive`: the highlighted danger item of a dark menu reaches 4.5:1 (it was 4.43:1).
- The `Drawer` × sits on the title line in every skin, also when there is a description.
- The `Slider` fill is centred on its track (it was 1 px off in `editorial`, `y2k`, `neumorphism` and `retro`).
- `retro`: the `AppShell` gets a window bevel. `neumorphism`: a softer light shadow.
## Menus, steppers, tables and more [#menus-steppers-tables-and-more]
30 Oct 2026 · library
- **Overlays.** Radio items and submenus in `Menu` and `ContextMenu`, checkbox items in `ContextMenu`, and Tab closes it. `Toggletip` no longer announces a dialog. A scrolling `DrawerBody` takes focus. A tall `Popover` is capped to the viewport. Toasts map the `loading` and `error` types and respect `limit`. Ctrl+K no longer opens the `CommandPalette` from another text field.
- **Display.** `sentiment` on `StatDelta`, for metrics where down is good. `EmptyStateTitle` takes `as`. `ProgressCircle` announces its value text. New `TableCaption` and `TableFooter`. A card can become one big link. `CodeBlock` scrolls as a named region and highlights comments per language. `Carousel` follows the tabs pattern.
- **Navigation.** `Stepper` gets `activeStep`, an error status and a styled `StepSeparator`. New `ToolbarInput`, and Home/End in toolbars. A tabs list scrolls when the tabs don't fit. `AccordionTrigger` takes a heading level. New `BreadcrumbEllipsis`.
- **App structure.** New `AppShellBody`, `render` on `AppShellContent` and `SidebarItem`, Enter collapses a `Splitter` panel, `ScrollArea` works with only a `max-height`, `VisuallyHidden` can show on focus.
- **Forms.** `RadioGroup` and `Slider` values are typed. `Slider` passes value text to its thumbs and has a vertical layout. `NativeSelect` joins `Field`. `NumberField` shows a decimal keypad for decimal steps. `OtpField` errors reach every cell. `TagsInput` no longer drops a repeated tag without telling `onValueChange`. `Checkbox` and `Rating` put the `id` on the focusable element.
## Missing tokens, stable slots and translated texts [#missing-tokens-stable-slots-and-translated-texts]
30 Oct 2026 · library
- Four tokens that components used but nobody defined are now in `tokens.css`: `--cml-border-subtle`, `--cml-item-hover`, `--cml-surface-subtle` and `--cml-motion-duration`.
- `data-slot` comes after `{...props}` in 183 places, so `render={}` keeps the button's slot and look.
- New `MessagesProvider`, `useMessages`, `Message` and the `en` and `ptBR` sets. The built-in texts of 23 components come from it; props still win. See [Languages & RTL](/docs/guides/i18n).
## A page for every component [#a-page-for-every-component]
28–30 Oct 2026 · docs
Every component got its own page, in English and Portuguese: a playground, 6 to 9 live examples, real compositions, do/don't pairs, props generated from the types, styling hooks and keyboard notes. They went group by group, from actions and forms to layout and app structure.
Writing them found and fixed bugs on the way, among them: 12-hour times breaking hydration, an indeterminate `Progress` that looked complete, `NavigationMenu` panels opening empty, `CommandPalette` arrows following the wrong order, `Stack` inside `Stack` inheriting the parent's gap, and focus falling to `` on the last page of a `Pagination`.
## The documentation site [#the-documentation-site]
27–28 Oct 2026 · docs
- Bilingual site with English at the root and Portuguese under `/pt-br`.
- The "preview in" bar: one skin, mode and brand for every preview, saved between visits.
- Previews with a Code tab that reads the example's own file, props tables generated from the types, and a page checker that runs axe in 4 skins, both modes, three widths and both languages.
- `Drawer` popup wrapped in its viewport, which removed a Base UI warning.
## Advanced forms, dates, data and app structure [#advanced-forms-dates-data-and-app-structure]
27 Oct 2026 · library
- **Advanced forms:** `PasswordInput`, `SearchInput`, `InputGroup`, `OtpField`, `TagsInput`, `Dropzone` with `FileList`, `Rating`, `Chip` and `ErrorSummary`.
- **Dates**, on React Aria: `Calendar`, `RangeCalendar`, `DatePicker`, `DateRangePicker`, `DateField` and `TimeField`, joined to `Field`.
- **Data display:** `DataTable` (TanStack Table), `Stat`, `DataList`, `Timeline`, `EmptyState`, `ProgressCircle`, `Meter`, `Image`, `CodeBlock`, `TreeView` and `Carousel`.
- **App structure:** `Sidebar`, `AppShell`, `NavigationMenu`, `Menubar`, `ContextMenu`, `Toolbar`, `Stepper`, `CommandPalette`, `HoverCard`, `Toggletip` and `Splitter`.
## Nine more skins [#nine-more-skins]
26–27 Oct 2026 · styles
- `neubrutalism`, `retro` and `glass`; then `swiss`, `progressive` and `clay`; then `material`, `cupertino` and `y2k`. Thirteen skins in all, each in light and dark.
- The contrast matrix learned to measure text over gradients and glass on real pixels. It caught the dark glass tab at 4.36:1 with a cyan brand on its first run.
## The first 50 components [#the-first-50-components]
26 Oct 2026 · library
About 50 components, in four groups: actions and forms, display and feedback (with tones checked at 4.5:1), navigation and overlays, and the layout primitives (`Box`, `Stack`, `Grid`, `Container`, `AspectRatio`, `ScrollArea`, `Portal`, `DirectionProvider`). Each one in the four first skins: `minimal`, `neumorphism`, `retro-pixel` and `editorial`.
## The style contract [#the-style-contract]
26 Oct 2026 · library
The idea the whole library rests on, proven with four skins as far apart as possible: fixed DOM with `data-slot` names, tokens in cascade layers (`cml.tokens`, `cml.components`, `cml.style`, `cml.preferences`), `StyleProvider` with nested islands, and `Button`, `Dialog` and `Tabs` as the first components.
## Foundation [#foundation]
26 Oct 2026 · tooling
The pnpm and Turborepo monorepo: `@camaleo/react`, `@camaleo/styles`, Storybook, this docs app and a Next.js app that checks server rendering.
# Accordion (/docs/components/accordion)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import what you need. It renders on the client.
## Anatomy [#anatomy]
`AccordionTrigger` puts its button inside a heading, so the sections show up in a screen reader's list of headings. It is an `h3`; set `level` to fit the page's outline, such as `level={4}` under a card title. The panel animates its height; the padding sits on an inner element so nothing jumps. `Collapsible` has the same trigger and panel without the heading.
## Examples [#examples]
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above. Open a section, then try Tab and Enter.
With `multiple`, opening a section leaves the others open. Use it when people read sections side by side; `defaultValue` opens the ones most people need.
Split long settings into sections people fill one at a time. `keepMounted` keeps the fields of closed sections in the form, so what people typed is still sent.
Click the button. With `value` and `onValueChange` you control which sections are open, so one button can open or close them all.
Press Ctrl+F and search for "invoice". With `hiddenUntilFound`, closed panels stay in the page and open when the browser finds a match in them.
A disabled header is skipped by Tab, so keep the reason in visible text next to it: say why it's closed and when it opens.
`Collapsible` hides options most people never need, so the form stays short and the defaults do the work.
Click it. The extra items appear right above the button, where the reader stopped, and the label says what it will do next.
## In context [#in-context]
These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
A question or a topic tells people whether to open it, and reads well in a list of headings.
"More information" and "Other" make people open every section to find anything.
Prices and limits in one table can be read at a glance.
With one plan open at a time, people have to remember the other one's price.
## Props [#props]
void", value: "Value[]", defaultValue: "Value[]" }}
>
`AccordionItem` takes a `value` and `disabled`. `AccordionTrigger` takes `level` (1 to 6, default 3) for the heading around it. `AccordionTrigger` and `AccordionPanel` take the props of their element.
`Collapsible` opens and closes one panel:
void" }}
/>
## Styling hooks [#styling-hooks]
Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the accordion itself.
```css
/* a plus that turns into a minus, only on the help page */
.help [data-slot="accordion"] {
--cml-accordion-chevron: none;
--cml-accordion-plus: inline-block;
}
```
## Accessibility [#accessibility]
## Related [#related]
# AlertDialog (/docs/components/alert-dialog)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import what you need. It renders on the client.
## Anatomy [#anatomy]
The same parts as [Dialog](/docs/components/dialog), so every skin styles both alike. The popup adds `data-kind="alert"` and `role="alertdialog"`.
## Examples [#examples]
Every example is a real file you can copy. Open them: the dialog wears the skin, mode and brand from the bar above.
Click Delete. The row and its button go away, so `finalFocus` moves focus to the list heading and a status message confirms it.
For the worst mistakes, like deleting a repository, make people type the name. The button stays disabled until it matches.
Click Sign out devices. While the request runs, Esc and Cancel do nothing, so people see whether it worked before the dialog closes.
Focus goes to the first focusable element. When that is a checkbox, point `initialFocus` at Cancel, so a quick Enter can't confirm.
Keep the dialog outside the menu and control it with `open`: the menu closes, the dialog stays, and `finalFocus` returns to the menu button.
Anything that can't be taken back deserves a confirm, like sending a newsletter. Use a `solid` button when it isn't destructive.
## In context [#in-context]
Confirms guard the actions that would hurt to get wrong. These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
"Delete 3 files" says what happens. People can answer without rereading the question.
Neither says what will happen, so people click through without reading.
Deleting a workspace that 12 people use is worth stopping for.
"Saved" doesn't need an answer. Interrupting for it teaches people to dismiss alerts. Use a toast.
## Props [#props]
void" }}
describe={{ open: "Whether the dialog is open. Use it with `onOpenChange` to control the state.", defaultOpen: "Whether the dialog starts open, without controlling the state.", onOpenChange: "Called when the dialog opens or closes. `eventDetails.reason` says why, for example `escape-key`. Ignore it to keep the dialog open while a request runs." }}
>
It is always modal and never closes on an outside click. The other parts take the props of their element, plus `render`.
`AlertDialogContent` decides where focus goes:
HTMLElement | boolean", finalFocus: "boolean | RefObject | (closeType) => HTMLElement | boolean" }}
describe={{ initialFocus: "The element that gets focus on open. By default, the first focusable element: put Cancel first.", finalFocus: "The element that gets focus on close. By default, the trigger. Set it when the trigger is gone after the action." }}
/>
## Styling hooks [#styling-hooks]
It shares the dialog's slots. Target `data-kind="alert"` to style only the confirms.
```css
/* narrower and centered, only in confirms */
[data-slot="dialog-popup"][data-kind="alert"] {
text-align: center;
inline-size: min(24rem, calc(100vw - 2rem));
}
```
## Accessibility [#accessibility]
## Related [#related]
# Alert (/docs/components/alert)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import what you need. It renders on the server.
## Anatomy [#anatomy]
A box with an icon picked by the tone, an optional label and the content. Every part has a `data-slot` you can target from CSS.
icontitleSign-in is down from 2:00 to 2:15 AM
## Examples [#examples]
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.
Pick the tone by what people must do: nothing (info), nothing more (success), act soon (warning), act now to continue (danger).
When one sentence says it all, skip the description. A short alert is read faster and gets in the way less.
If people can fix it, put the button inside the alert. They don't have to look for the right settings page.
Put the alert right above the field it is about, so people read the risk before they change the value.
Click Dismiss. Only notices people can live without should close. Focus moves to the button that replaces the alert, so keyboard users don't lose their place.
`warning` and `danger` get `role="alert"` and interrupt the screen reader; `info` and `success` get `role="status"` and wait their turn.
Swap the icon when your product has a clearer one, like a sparkle for AI features. The tone still sets the color and the role.
## In context [#in-context]
Alerts sit next to what they are about. These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
Save danger for what really blocks them, so red keeps its meaning.
When everything is red, people stop reading, and miss the real errors.
Name the problem and give the next step, ideally as a button.
"Something went wrong" leaves people stuck and sends them to support.
## Props [#props]
`AlertTitle` and `AlertDescription` render a `
` and take its attributes, like `className` and `style`.
## Styling hooks [#styling-hooks]
Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the alert itself.
## Accessibility [#accessibility]
## Related [#related]
# AppShell (/docs/components/app-shell)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import what you need. It has no state, so it also renders on the server.
## Anatomy [#anatomy]
A column the height of the screen, with the safe areas of notched phones as padding. The parts are plain landmarks: the header is the page's banner, the content is its `main`. For a sidebar, put `AppShellBody` under the header: a row with the `Sidebar` and `AppShellContent`, where the content takes the rest of the width, also beside a rail.
## Examples [#examples]
Every example is a real file you can copy, shrunk to fit a tile. The docs page around them already has a `main`, so each tile's `AppShellContent` takes `render={}` and an id from `useId`; in your app, drop both.
`AppShellBody` puts the sidebar in a row under the header. Press the toggle at the bottom of the rail to show the labels; the content takes the rest of the width.
The header says where you are and gives a way to jump anywhere. Press Search: the command palette opens and goes to a page.
Give the content area `overflow-y: auto` and the shell a fixed height. The search and the mailboxes stay in view while the messages scroll.
A `SidebarNav` inside the content lists the sections of one page. When the frame is narrow it moves above the form instead of squeezing it.
Click above the frame and press Tab. `SkipLink` appears, and Enter moves focus past the header to the content, so keyboard users don't tab through every link on every page.
The shell doesn't change by itself: you pick a layout per width. Narrow this window to see the full sidebar become a rail, then a menu button with a drawer.
## In context [#in-context]
Phone-sized screens you can copy, in the skin you picked. Header and footer stay put; the middle scrolls.
## Guidelines [#guidelines]
Scroll the list: the header, with search and the account, is always one click away.
Scroll this one. When the whole frame scrolls, people have to go back to the top to reach the menu.
The app's name, search, notifications and the account. Page actions belong in the page.
Buttons that only work on one page are noise on all the others, and they wrap on small screens.
## Props [#props]
`AppShell`, `AppShellHeader` and `AppShellFooter` take the props of a `div`, `header` and `footer`. `AppShellBody` takes the props of a `div`. `AppShellContent` takes the props of `main`, and its `id` defaults to `"main-content"`, the target `SkipLink` uses when you don't pass `targetId`. Change both together, and keep the id unique. `render` swaps the `main` for another element, such as `render={}` when the page already has a `main`; the id stays, so the skip link still works.
## Styling hooks [#styling-hooks]
Restyle it without forking: target the slots, or set the tokens on a parent, a skin or the shell itself.
```css
/* a taller header, and a content area that scrolls on its own */
.app [data-slot="app-shell"] {
--cml-app-shell-header-height: 4rem;
height: 100dvh;
}
.app [data-slot="app-shell-content"] {
overflow-y: auto;
}
```
## Accessibility [#accessibility]
## Related [#related]
# AspectRatio (/docs/components/aspect-ratio)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import it. It has no state, so it renders on the server.
## Anatomy [#anatomy]
One box with `position: relative` and hidden overflow. A direct `img`, `video` or `iframe` fills it; anything else you lay on top with `position: absolute`.
## Examples [#examples]
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.
Switch the ratio. `ratio` is width divided by height, so write it as a fraction: `16 / 9` for video, `4 / 3` for photos, `1` for squares, `3 / 4` for portraits.
`cover`, the default, fills the box and crops what doesn't fit. Set `objectFit: "contain"` on the image when every edge matters, like a product or a logo.
The three photos are 16:9, 4:3 and 3:4. Cut to the same ratio, the cards are the same height and the titles line up.
Square thumbnails make a tidy grid whatever was uploaded. `object-position` chooses which part of the photo survives the crop.
An `iframe` fills the box like an image does. Give it a `title` that says what's inside: it is the frame's name for screen readers.
Reload the photo. The box has its height before the file arrives, so the text below never jumps, and a status message says when it's ready.
## In context [#in-context]
Media rarely stands alone. These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
Photos of any shape end up the same size, and the titles line up.
The row goes ragged and the eye follows the gaps instead of the content.
Their edges carry meaning; show all of it, with some padding.
The crop cuts off the brand name, which is the whole point.
## Props [#props]
Every attribute of a `
` passes through, like `style` and `className`. Set a width (or let the parent's grid set it); the height always follows the ratio.
## Styling hooks [#styling-hooks]
Restyle it without forking: target the slot, or set the custom property in your own CSS.
```css
/* wider video on large screens, taller on phones */
.lesson [data-slot="aspect-ratio"] {
--cml-aspect-ratio: 4 / 3;
}
@media (min-width: 48rem) {
.lesson [data-slot="aspect-ratio"] {
--cml-aspect-ratio: 16 / 9;
}
}
```
## Accessibility [#accessibility]
## Related [#related]
# Avatar (/docs/components/avatar)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import what you need. It renders on the client, because it waits for the photo to load.
## Anatomy [#anatomy]
A round box that holds the photo, or the fallback while the photo loads or when it fails. `AvatarGroup` overlaps several of them.
## Examples [#examples]
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.
On its own, the avatar must say who it is. Put the person's name in `alt`, never "photo" or "avatar".
When the name is right beside it, hide the avatar with `aria-hidden`. Otherwise screen readers say the name twice.
Small for comments and dense lists, medium for menus and cards, large for the top of a profile. Keep one size per list.
Without a photo, two initials still tell people apart. Use the icon for guests and accounts without a name. `alt` names the avatar whether the photo or the fallback is showing, so screen readers hear the name, not the initials.
If the photo doesn't load, the initials stay in place. Always pass a `fallback`, so a broken link never leaves an empty circle.
Show a few faces and a "+3" for the rest. Name the group with the total, like "7 people on…", and give "+3" its own name.
Click the button. The status is a word in a badge, not just a green dot, and a status region reads the change aloud.
## In context [#in-context]
Avatars put a face next to what people did. These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
With the name next to it, the avatar is decoration: hide it from screen readers.
Screen readers hear "Photo of Mariana Lima, Mariana Lima". The repeat adds nothing.
First and last name initials tell people apart, even without a photo.
"?" looks like an error and says nothing about who it is.
## Props [#props]
Other attributes, like `role`, `aria-label` and `aria-hidden`, go to the outer ``. `AvatarGroup` renders a `
` and needs an `aria-label`.
## Styling hooks [#styling-hooks]
Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the avatar itself.
## Accessibility [#accessibility]
## Related [#related]
# Badge (/docs/components/badge)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import what you need. It renders on the server.
## Anatomy [#anatomy]
A `` with the label and, if you ask for it, a small dot before it. Every part has a `data-slot` you can target from CSS.
## Examples [#examples]
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.
The soft fill is the everyday choice. Save the solid one for the badge that must stand out on the screen, and use the outline when many badges sit together.
Pick the tone by meaning: grey for neutral, blue for information, green when it went well, yellow when it needs attention soon, red when something failed.
The dot makes a live state, like online or in a meeting, easy to spot in a list. The word next to it is what carries the meaning.
An icon helps people scan, but the word must say it all. Mark the icon `aria-hidden` so it isn't read twice.
A screen reader that hears "Inbox 3" doesn't know three of what. Add "unread" as hidden text inside the badge.
Labels typed by users can get long. Cap the width and cut the text with an ellipsis; the full name stays in the page for screen readers and in `title` for the mouse.
One badge per row, in its own column, lets people compare statuses down the table at a glance.
Click a filter. The badge only shows the count; the `Toggle` around it is what people press, and it announces whether it is on.
## In context [#in-context]
Badges sit next to what they describe. These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
"Payment settled" is clear to everyone, with or without the color.
"Status 200" means something to the developer, not to the customer reading it.
A short badge reads in a glance and fits on a phone.
Long text gets cut off or breaks the layout. Put explanations in the text, or in an Alert.
## Props [#props]
`Badge` renders a `` and takes its attributes, like `className`, `style` and `title`.
## Styling hooks [#styling-hooks]
Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the badge itself.
## Accessibility [#accessibility]
## Related [#related]
# Box (/docs/components/box)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import it. It has no state, so it renders on the server.
## Anatomy [#anatomy]
Box is one element with `data-slot="box"` and no styles of its own. Its value is the tag: here a section holds a header and a list, each one a Box with a different `as`.
## Examples [#examples]
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.
Three files are a list, so render `ul` and `li`. Screen readers announce "list, 3 items" and people know how much is coming.
`as="article"` for content that still makes sense on its own, like a post or a comment. Its `header` and `footer` stay inside it and are not page landmarks.
`as="nav"` with an `aria-label` lets screen reader users jump straight to these links. Mark the current page with `aria-current="page"`.
A form with `role="search"` becomes a landmark people can reach from anywhere. Name it when the page has more than one search.
Switch it off and on. Steps that must happen in sequence are an `ol`; the numbers come from the browser and screen readers say "1 of 4".
Box has no padding or background props. Style it with `var(--cml-…)` tokens and it changes with the skin, like the components around it.
## In context [#in-context]
These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
Screen readers count the items and let people skip the whole group.
It looks the same, but a screen reader hears three loose words with no hint that they belong together.
People who jump between landmarks expect to find the site's or the section's navigation there.
Filters change the list on this page; they are not navigation. A plain group (or a Toolbar) keeps the landmark list short and honest.
## Props [#props]
`as` takes `div`, `section`, `article`, `main`, `aside`, `header`, `footer`, `nav`, `ul`, `ol`, `li`, `span`, `form` and `figure`. Every other prop goes to that element, so `aria-*`, `role`, `id` and event handlers work as usual.
## Styling hooks [#styling-hooks]
Box draws nothing, so the hook is the attribute: style it from a parent or a class.
```css
/* a quiet panel for every Box section in the settings page */
.settings [data-slot="box"]:where(section) {
padding: var(--cml-surface-padding);
border-radius: var(--cml-radius-surface);
background: var(--cml-surface);
}
```
## Accessibility [#accessibility]
## Related [#related]
# Breadcrumb (/docs/components/breadcrumb)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import what you need. It has no state and no effects, so it renders on the server too; `BreadcrumbLink` takes a `render` prop for your router's link.
## Anatomy [#anatomy]
A `nav` landmark around an ordered list. Each level is an item; separators are items of their own, hidden from screen readers, and the last item is the current page.
## Examples [#examples]
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.
Mirror the catalog tree, so someone who landed on a category from a search can go up a level and browse its neighbors.
Deep settings screens are easy to get lost in. The trail ends with the page's own heading, which repeats it as the title.
Press the … button. Keep the first level and the last two in view and put the rest in a `Menu` opened by a `BreadcrumbEllipsis`; its name says how many levels it hides.
A house alone means nothing to a screen reader. Give an icon-only link a name with `VisuallyHidden`, and mark every icon `aria-hidden`.
Clip a long name on screen with an ellipsis, never in the text: screen readers still read the whole name, and the heading below shows it in full.
Every separator holds a chevron and a slash. Set `--cml-crumb-chevron: none` and `--cml-crumb-slash: inline` for file paths, where the slash is the convention.
`render={}` keeps client-side navigation and prefetching, and the link keeps its slot, its style and its focus ring.
The list wraps to a second line instead of scrolling sideways. When it would take three lines, collapse the middle levels.
## In context [#in-context]
These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
The same order is always under Orders, however people got to it.
That is what the Back button is for. A trail that changes with every visit can't teach the structure.
`BreadcrumbPage` says "you are here" with `aria-current="page"` and isn't a link to itself.
It looks like somewhere else to go, and following it only reloads the same page.
## Props [#props]
`BreadcrumbItem`, `BreadcrumbPage` and `BreadcrumbSeparator` take the props of their `li`, `span` and `li`. `BreadcrumbLink` takes the props of an `a`, plus `render` to use your router's link instead. `BreadcrumbEllipsis` stands for collapsed levels: alone it is an image named "More pages"; with `render={}`, as a menu's trigger, it is a button. Pass `aria-label` to say how many levels it hides.
## Styling hooks [#styling-hooks]
Restyle it without forking: target the slots, or set the tokens on a parent, a skin or the breadcrumb itself.
```css
/* slashes instead of chevrons in the file browser */
.files [data-slot="breadcrumb"] {
--cml-crumb-chevron: none;
--cml-crumb-slash: inline;
}
```
## Accessibility [#accessibility]
## Related [#related]
# Button (/docs/components/button)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import what you need. The skin you load styles it.
## Anatomy [#anatomy]
One element with up to three parts. Every part has a `data-slot` you can target from CSS.
spinnericonPublish
## Examples [#examples]
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.
`sm`, `md` and `lg` follow the skin's control heights, so a form and its buttons always line up.
Put the icon before the label for something new, after it for direction or download. Mark it `data-slot="icon"` and `aria-hidden`.
`IconButton` requires `aria-label`. Pair it with a `Tooltip` so sighted users get the name too.
Navigation is a link, not a button. `render={}` with `nativeButton={false}` keeps the look and gives you a real anchor.
Click it. `loading` keeps the width and the focus while the request runs; a status message announces the result.
Click it. A `danger` action opens an `AlertDialog`. The safe choice comes first and gets focus.
`ButtonGroup` joins related actions under `role="group"`. For arrow-key navigation between them, use `Toolbar`.
Never leave people guessing. Say what is missing and link it with `aria-describedby`, or keep the button enabled and validate on submit.
## In context [#in-context]
Buttons are never alone. These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
It tells people what the main action is.
Nothing stands out, so nothing is clear.
Say what happens: "Create invoice", "Save changes".
"OK" and "Click here" mean nothing out of context, or to a screen reader listing the buttons.
## Props [#props]
ReactElement" }}
describe={{ disabled: "Disables the button and removes it from the tab order." }}
>
`IconButton` takes the same props without `size`, plus a required `aria-label`. Native button attributes pass through.
## Styling hooks [#styling-hooks]
Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the button itself.
```css
/* a pill-shaped primary, only in this section */
.checkout [data-slot="button"][data-variant="solid"] {
--cml-radius-control: 999px;
}
```
## Accessibility [#accessibility]
## Related [#related]
# Calendar (/docs/components/calendar)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import what you need. Dates are `@internationalized/date` objects, re-exported from Camaleo.
## Anatomy [#anatomy]
A header with the month and two arrows, the grid of days and an optional footer. Every part has a `data-slot` you can target from CSS.
title‹ ›gridfooter
## Examples [#examples]
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.
Pick a day. `value` and `onChange` keep the date in your state, and the sentence below spells it out with the weekday.
`minValue` and `maxValue` disable the days outside the window, and the arrows stop at its first and last month.
`isDateUnavailable` with `isWeekend` follows the locale. Unavailable days stay focusable and are announced as unavailable, so say why in the footer.
Return `true` for the days your API says are full. The strike-through is not the only signal: the footer explains it in words.
The locale sets the first day: Sunday in the US and Brazil. `firstDayOfWeek="mon"` matches a team that plans in ISO weeks.
`readOnly` shows a confirmed date that people can still browse and read, unlike `disabled`, which drops it from the tab order.
With no value the calendar opens on today. `defaultFocusedValue` opens it on the month people need, and renders the same on server and client.
## In context [#in-context]
Calendars sit inside bookings and checkouts. These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
A short line in the footer answers the question before people click around.
People try them one by one, and a screen reader only hears "unavailable".
The month on screen is the one people pick from.
Reaching 1994 takes hundreds of clicks. Use a [Date field](/docs/components/date-field) and let people type it.
## Props [#props]
void", minValue: "DateValue | null", maxValue: "DateValue | null", isDateUnavailable: "(date: DateValue) => boolean", firstDayOfWeek: "\"sun\" | \"mon\" | \"tue\" | \"wed\" | \"thu\" | \"fri\" | \"sat\"", focusedValue: "DateValue | null", defaultFocusedValue: "DateValue | null", onFocusChange: "(date: CalendarDate) => void" }}
describe={{ className: "Class names for the root element.", style: "Inline styles for the root element.", disabled: "Disables every day and the arrows, and removes the grid from the tab order.", readOnly: "Shows the value without letting people change it. Days stay focusable.", invalid: "Marks the selection as invalid, for errors that come from your app.", locale: "BCP 47 locale for month names, weekdays and the first day of the week, e.g. `\"pt-BR\"`. Defaults to the nearest `I18nProvider` or the browser.", weekdayStyle: "How the weekday headers are written: `\"narrow\"`, `\"short\"` (default) or `\"long\"`.", previousLabel: "Accessible name of the previous-month arrow. React Aria's localized \"Previous\" by default.", nextLabel: "Accessible name of the next-month arrow. React Aria's localized \"Next\" by default.", footer: "Content under the grid: the chosen date, a legend or a price." }}
>
Dates are `CalendarDate` objects from `@internationalized/date`, re-exported by Camaleo with `parseDate`, `today` and `isWeekend`. React Aria's other calendar props pass through, such as `pageBehavior` and `createCalendar`.
## Styling hooks [#styling-hooks]
Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the calendar itself.
```css
/* round day cells, only in the booking flow */
.booking [data-slot="calendar"] {
--cml-calendar-cell-radius: 999px;
}
```
## Accessibility [#accessibility]
## Related [#related]
# Card (/docs/components/card)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import what you need. It renders on the server.
## Anatomy [#anatomy]
A header with the title and a short description, the content, and a footer for actions. Use only the parts you need; every part has a `data-slot` you can target from CSS.
## Examples [#examples]
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.
Put the buttons in `CardFooter`, the main one last. People find them in the same place on every card.
Click anywhere on the card. The link stays in the title, so screen readers hear a short name, and `data-card-link` on it stretches it over the card. Don't turn the card itself into a button.
Click a card or use the arrow keys. Each card is a radio's label, so the whole card selects it, and the legend names the group.
A picture on top helps people recognize a guide or a product. If it only decorates, hide it with `aria-hidden`.
Less padding for dashboard numbers. Keep the title: it is what names the number for everyone.
An empty card still shows its title, says why it is empty and offers the next step, instead of disappearing.
A list of cards is a list: use `ul` and `li`, so screen readers say how many there are. The grid wraps on its own on phones.
## In context [#in-context]
Cards hold one subject each. These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
They set the spacing and the title style, so the card looks right in every skin.
The title is no longer a heading, and the spacing breaks when people switch skins.
A separator divides the content and keeps one box around one subject.
Boxes inside boxes waste space on phones and make it unclear what belongs together.
## Props [#props]
`Card` and each part render plain HTML and take its attributes: `Card`, `CardHeader`, `CardContent` and `CardFooter` a `
`, `CardTitle` an `
`, `CardDescription` a `
`.
## Styling hooks [#styling-hooks]
Restyle it without forking: target the slots, or set the tokens on a parent, a skin or the card itself.
## Accessibility [#accessibility]
. If the page structure needs another level, render a different heading inside it or set aria-level.",
"For a clickable card, put one link in the title and add data-card-link to it. Wrapping the whole card in a link or button makes screen readers read everything as one long name.",
"Other links, buttons and fields on a data-card-link card stay above the stretched link and keep working. Keep them few: the rest of the card can't be selected as text.",
"Selectable cards are radios or checkboxes with a label that fills the card, inside a fieldset with a legend.",
]}
/>
## Related [#related]
# Carousel (/docs/components/carousel)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import what you need. It runs on the client, because it keeps the current slide.
## Anatomy [#anatomy]
A `` with the slides, then a row of controls. `CarouselSlide` is an optional layout for each slide; any element works. Every part has a `data-slot` you can target from CSS.
## Examples [#examples]
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.
Each dot is a tab: Tab reaches the row once, and the arrow keys move between slides. Hide the Play button with `showAutoPlayToggle={false}` when people read at their own pace.
Keep `autoPlay` off and leave the Play button: people who want the slides to move press it, and Pause stops them. Rotation never runs when the system asks for reduced motion.
`CarouselSlide` lays out `kicker`, `title`, `description` and `badge`. Put the date or category in the kicker, so the title stays short enough for a phone.
Any element can be a slide. Start it with a heading, because the slide's own name is only its position, like "2 of 3".
Move through the steps, then press Start over. Control `activeIndex` when the page must know the slide, like for a step counter or a finish button.
Screen readers hear each slide as "2 of 3, slide". Pass `slideLabel`, `slideRoleDescription` and the button labels to say "Plan 2 of 3" in the page's language.
## In context [#in-context]
Carousels usually sit in a card or a page section, next to content that is always visible. These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
People rarely go past the third slide, so a short set gets seen and the dots stay easy to tell apart.
Eight dots look the same, and the last slides are only seen by people who click through all of them.
People scanning, and screen reader users jumping by heading, know what a slide is about before reading it.
Without a title, people have to read the whole slide to decide if it matters to them.
## Props [#props]
string", goToSlideLabel: "(index) => string" }} />
Both take the attributes of their element: `Carousel` those of `` (its `aria-label` defaults to "Carousel", so always pass one), `CarouselSlide` those of `
`, with `children` after the description.
## Styling hooks [#styling-hooks]
Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the carousel itself.
## Accessibility [#accessibility]
## Related [#related]
# Checkbox (/docs/components/checkbox)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import what you need. Wrap each checkbox in a `Label` so the text is clickable and names it.
## Anatomy [#anatomy]
A `Checkbox` is a box with an indicator inside. A `CheckboxGroup` wraps several labeled checkboxes and keeps their values together.
checkboxProduct updates
## Examples [#examples]
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.
`Fieldset render={}` gives the options one name, so a screen reader announces "Email me about" before each of them.
Click the children. With `allValues` on the group and `parent` on one box, it shows the mixed state when only some are ticked.
Keep the label short and put the consequence below it, linked with `aria-describedby` so it is read after the name.
Submit it unticked. Say what to do in the error, not just "Required"; `Field` links it to the checkbox.
A disabled box still shows what is included. When it is off, say why, so people know how to get it.
In a table the row is the label, so give each checkbox an `aria-label` that names its row.
## In context [#in-context]
Checkboxes live in forms and lists. These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
Each one stands alone: people can pick none, one or all of them.
Monthly and yearly at once makes no sense. Use a `RadioGroup`.
A ticked box should mean yes: "Send me the weekly digest".
A tick that means "don't stop" makes people stop and reread.
## Props [#props]
void" }}
describe={{ value: "Identifies the checkbox inside a `CheckboxGroup` and is the value submitted with the form.", parent: "Whether the checkbox controls the children of a `CheckboxGroup` (the \"select all\" box)." }}
>
`CheckboxGroup` takes `value`, `defaultValue` and `onValueChange` (arrays of the checkboxes' `value`), `allValues` for a parent checkbox, and `disabled`. Native attributes pass through.
## Styling hooks [#styling-hooks]
Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the checkbox itself.
## Accessibility [#accessibility]
## Related [#related]
# Chip (/docs/components/chip)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import what you need. The skin you load styles it.
## Anatomy [#anatomy]
A filter chip is a toggle button with a check that shows while pressed. An input chip (with `onRemove`) is a span with a remove button at the end.
checkIn reviewremove
## Examples [#examples]
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.
Press the chips. Counts in the labels set expectations, and a `role="status"` line announces how many results are left.
`multiple={false}` keeps one chip pressed at a time, but people can still unpress it. When an empty choice is not allowed, use a radio group.
Remove a label. `onRemove` turns the chip into an input chip, `removeLabel` names its button "Remove Urgent", and focus stays in the list instead of falling to the page body.
`avatar` takes a small `Avatar`. Its initials are decoration: the name in the chip and in `removeLabel` is what assistive tech reads.
A single `Chip` is a toggle with `aria-pressed`, good for one quick filter like "Only unread" above a list.
An icon helps people scan a long set. Mark it `data-slot="icon"` and `aria-hidden`; the text still carries the meaning.
Arrow keys skip a disabled chip. Say why it is off in text linked with `aria-describedby`, or people think the filter is broken.
## In context [#in-context]
Chips sit above results, inside filter panels and in share dialogs. These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
Short labels keep a row of chips scannable and fit on a phone.
They break the row into ragged lines and take longer to scan than one or two words.
Pressing a chip changes what you see; an action like "Export" stays a button.
A chip announces "pressed" or "not pressed", which makes no sense for "Delete".
## Props [#props]
ReactElement", onPressedChange: "(pressed, details) => void" }}
>
`ChipGroup` takes `value`, `defaultValue` and `onValueChange` (arrays of chip values), `multiple` (`true` by default), `disabled` and `orientation`. Give it a name with `aria-label` or `aria-labelledby`.
## Styling hooks [#styling-hooks]
Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the chip itself.
```css
/* square chips, only in this toolbar */
.report-filters [data-slot="chip"] {
--cml-chip-radius: var(--cml-radius-control);
}
```
## Accessibility [#accessibility]
## Related [#related]
# CodeBlock (/docs/components/code-block)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import what you need. It is a client component, because of the copy button.
## Anatomy [#anatomy]
A header with the file name, the language and the copy button, then a `
` that scrolls sideways when lines are long. Every part has a `data-slot` you can target from CSS.
## Examples [#examples]
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.
When people must paste the code into a specific file, put its path in `filename`. The `language` badge tells them the syntax at a glance.
Turn off line numbers for a one-line command: there is nothing to point at, and the command reads as one piece.
The built-in highlighter colors strings, comments and JavaScript keywords. That is enough for JSON or a shell command. `language` also sets the comments: `#` for Bash, Python or YAML, `//` for the rest.
Pass `highlight` to render each line yourself, like the lines of a diff. Keep the + and - in the text, so the change doesn't depend on color.
Long lines scroll sideways instead of wrapping, so a command stays copyable as it is. Keyboard users Tab into the code and scroll with the arrow keys.
Pick a tab. Tabs show one command at a time and keep the choice clear, instead of four blocks stacked on top of each other.
Say what the button copies in `copyLabel`, like "Copy install command". After the copy, `copiedLabel` shows on the button and screen readers hear it too.
## In context [#in-context]
Code blocks usually sit in a card or a guide, right after the sentence that explains them. These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
A file path like `app/blog/page.tsx` tells people which file to open before they paste.
People have to guess whether the line goes in a page, a layout or a config file.
Cut the rest with a `// …` comment. People see the change at once and copy less by mistake.
People have to hunt for the line you mean, and the copy button copies all of it.
## Props [#props]
ReactNode" }}
>
Native `
` attributes go to the root. There is no prop for the `
` or a callback for the copy.
## Styling hooks [#styling-hooks]
Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the block itself.
## Accessibility [#accessibility]
## Related [#related]
# Combobox (/docs/components/combobox)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import the parts you need, plus `ComboboxEmpty` for the no-results message. The skin you load styles the input and the popup.
## Anatomy [#anatomy]
`Combobox` holds the items and the value; `ComboboxInput` is the text field with its clear and open buttons; the content is a popup rendered in a portal. Every part has a `data-slot` you can target from CSS.
Curitiba✕⌄
## Examples [#examples]
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.
Type a name that isn't there. `ComboboxEmpty` only shows when nothing matches, so use it to say what to do next, not just "No results".
Store the code, show the name: `itemToStringLabel` fills the input, `itemToStringValue` goes to the form, `isItemEqualToValue` compares them.
Pick someone. With `value` and `onValueChange` the rest of the screen can follow the choice as it happens.
Type "gru" or "galeao". A `filter` function can match codes, nicknames or text without accents, whatever people actually type.
`autoHighlight` highlights the first result as you type, so a short query and Enter is enough for tags and labels.
The clear and open buttons are icons. Translate their names with `clearLabel` and `triggerLabel`, and make them specific to the field.
Inside a `Field`, the label names the input and `FieldError` is read with it; tell people to pick from the list, since free text won't be accepted.
## In context [#in-context]
A combobox is usually one field among several. These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
Nobody wants to scroll through 27 state capitals; typing "flo" is faster.
Typing is extra work when everything fits on screen; use a Select or radios.
A label, an example in the placeholder and a hint about what is covered.
Search what? With no visible label, people guess the scope and the format.
## Props [#props]
ReactElement" }}
>
These are the props of `ComboboxInput`; other input attributes like `placeholder` pass through. `Combobox` takes Base UI's root props: `items`, `value`, `defaultValue`, `onValueChange`, `inputValue` / `onInputValueChange`, `filter`, `itemToStringLabel`, `itemToStringValue`, `isItemEqualToValue`, `autoHighlight`, `limit`, `multiple`, `disabled`, `required` and `name`. `ComboboxList` takes a function of each item; `ComboboxItem` takes `value` and `disabled`.
## Styling hooks [#styling-hooks]
Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the combobox itself.
## Accessibility [#accessibility]
## Related [#related]
# CommandPalette (/docs/components/command-palette)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import what you need. It renders on the client.
## Anatomy [#anatomy]
`CommandPaletteContent` renders the backdrop and the dialog in a portal. Focus stays in the search; the arrow keys move a highlight through the list with `aria-activedescendant`.
## Examples [#examples]
Every example is a real file you can copy, in the skin, mode and brand from the bar above. Each one starts closed: open it, type, use the arrows and Enter, then Esc. The palette doesn't filter on its own. Keep the query in state, render only the commands that match, and give `CommandPaletteList` a `key={query}`, so the arrow keys follow the new order.
Press Ctrl+K anywhere on this page. The button stays as the visible way in, and each command shows its own shortcut so people learn it.
Headings help people scan a long list. A group with no matches disappears with its heading, and the highlight scrolls into view.
Type "receipt" or "add user". People search with their own words, so match keywords too and say which one matched.
Repeat what was searched and suggest what to try. The query stays in the box so it's easy to fix.
Type a name. While the request runs, the list has `aria-busy` and the footer says "Searching…"; a newer search makes the old one stale.
"Change theme…" opens a page of options. Esc or Backspace in an empty search goes back one level before closing.
The footer count is a polite status, read when typing pauses. After a command runs, a status outside the palette says what happened.
Keep it in the list with `aria-disabled` and say why. People find it, learn who can do it, and Enter does nothing.
## In context [#in-context]
These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
Creating and exporting have their own buttons. The palette is the faster way for people who know what they want.
Nobody browses a search box. If "Create invoice" isn't on the page, most people never find it.
"Send payment reminder" says what will happen, and the first word is what people type.
"Invoice" could open, create or delete one. A sentence gets cut off and is hard to match.
## Props [#props]
void" }}
describe={{ open: "Whether the palette is open. Use it with `onOpenChange` to control the state.", defaultOpen: "Whether it starts open. Leave it off: a palette should open when people ask for it.", onOpenChange: "Called when it opens or closes. `eventDetails.reason` says why, for example `escape-key`.", modal: "`true` traps focus and hides the page behind from screen readers. Keep the default." }}
>
`CommandPaletteContent`, `CommandPaletteList`, `CommandPaletteEmpty` and `CommandPaletteFooter` take the props of their `div`; pass `aria-label` to the content and the list. `CommandPaletteInput` takes the props of an `input`, including `value`, `onChange` and `aria-label`. `CommandPaletteTrigger` takes `render`.
Each command is a `CommandPaletteItem`:
`onSelect` runs the command, on a click or on Enter while the item is highlighted. It also takes the props of its `div`, like `aria-disabled`, `aria-keyshortcuts` and `onMouseDown`. `CommandPaletteGroup` takes `label`, its visible heading.
## Styling hooks [#styling-hooks]
Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the palette itself.
```css
/* a taller list for the file finder */
.editor [data-slot="command-palette-list"] {
max-height: 24rem;
}
```
## Accessibility [#accessibility]
## Related [#related]
# Container (/docs/components/container)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import it. It has no state, so it renders on the server.
## Anatomy [#anatomy]
One element. The size arrives as the `--cml-container-max` custom property, so your own CSS can still change it.
## Examples [#examples]
Every example is a real file you can copy. The bordered box in each one stands for the browser window; widen and narrow your own window to see the column follow.
Pick a size. The drawing is a 1440 px screen shrunk to fit, so you see the real proportions: `sm` for reading and forms, `lg` (the default) for most pages, `xl` for tables.
Long text reads best at 60–75 characters a line. Set `--cml-container-max: 65ch` and the column follows the font, not the screen.
Put the background on an outer `section` and the Container inside it: the colour runs edge to edge, the text stays in line with the rest of the page.
Turn on the wider gutter. Below its maximum, the column fills the screen but keeps 1rem at each side; change `--cml-container-padding` for more air.
A sign-up form doesn't need the page's width. `size="sm"` keeps every label, field and the button within one glance.
Container is a plain `div`. Pass `as="main"`, `as="article"` or `as="section"` with a label, so screen reader users can jump straight to the column.
## In context [#in-context]
Containers frame whole pages. These are pieces you can copy, in the skin you picked.
## Guidelines [#guidelines]
The band reaches both edges on any screen, and its text lines up with the page.
On wide screens the colour stops at the column and looks like a misplaced card.
Short lines let the eye find the start of the next one.
Lines of 150 characters are tiring, and readers lose their place.
## Props [#props]
Every attribute of the rendered element passes through, like `id`, `aria-labelledby` or `style`. Don't nest containers: the padding adds up on phones.
## Styling hooks [#styling-hooks]
Restyle it without forking: set the custom properties on the container, on a parent or in a skin.
```css
/* a roomier gutter on tablets and up */
@media (min-width: 48rem) {
[data-slot="container"] {
--cml-container-padding: 2rem;
}
}
```
## Accessibility [#accessibility]
## Related [#related]
# Context menu (/docs/components/context-menu)
## Installation [#installation]
Set up Camaleo once ([Installation](/docs/installation)), then import the parts you need. The popup renders in a portal, at the pointer, and keeps the skin, mode and brand of the page around it.
## Anatomy [#anatomy]
`ContextMenu` holds the open state. `ContextMenuTrigger` (`data-slot="context-menu-trigger"`) is the area people right-click; `ContextMenuContent` renders the popup below in a portal. Every part has a `data-slot` you can target from CSS.
## Examples [#examples]
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above, and they all start closed: right-click them.
The classic use: the actions for one file, where the pointer already is. `tabIndex={0}` lets keyboard users focus the row and open the menu with Shift+F10 or the Menu key.
On an empty board there is nothing to put a button on, so the right-click is where people look. Keep the same commands in a toolbar.
A `ContextMenuGroupLabel` over each group helps people scan a longer menu; the destructive group goes last.
People who right-click are learning. Show the shortcut with `Kbd` and add `aria-keyshortcuts`, so next time they skip the menu.
Right-click the note and pick an action. Focus goes back to the note, and a `role="status"` line says what happened.
A context menu is hidden by nature. Put the same actions behind a "⋯" button, from one shared list, so touch, keyboard and screen reader users find them.
Right-click the board and change its settings: `ContextMenuCheckboxItem` and `ContextMenuRadioItem` keep the menu open and report `aria-checked`, so people can change several at once.
Put a list of destinations behind one "Move to" item instead of stretching the menu; → opens it, ← goes back, and the current folder is disabled.
## In context [#in-context]
A context menu speeds up work that already has a visible path. These are whole pieces of UI you can copy, in the skin you picked.
## Guidelines [#guidelines]
The right-click is a shortcut for people who know it; the "⋯" button is the way in for everyone else.
Nothing on screen says they exist, and on a phone or with a screen reader nobody finds them.
Right-click a comment: reply, edit, copy. The menu answers "what can I do with this?".
Settings and Sign out have nothing to do with the comment, and they belong in the header where people expect them.
## Props [#props]
ReactElement" }}
describe={{ onClick: "Runs when the item is chosen with a click, Enter or Space.", closeOnClick: "Whether choosing the item closes the menu.", disabled: "Disables the item. It stays focusable, so people can still read it.", label: "Text for typeahead when the item's content is not plain text.", render: "Renders another element, such as an `` for a link." }}
>
These are the props of `ContextMenuItem`. `ContextMenu` takes Base UI's root props: `open`, `onOpenChange`, `disabled` and `actionsRef`. `ContextMenuCheckboxItem` takes `checked`, `defaultChecked`, `onCheckedChange` and `shortcut`; `ContextMenuRadioGroup` takes `value`, `defaultValue` and `onValueChange`, and each `ContextMenuRadioItem` a `value`. Wrap a `ContextMenuSubTrigger` and its `ContextMenuSubContent` in a `ContextMenuSub`. `ContextMenuTrigger` renders a `