# 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={