# Accessibility (/ui/docs/react/accessibility) Accessibility is built in, not bolted on. Appica UI wraps [Base UI](https://base-ui.com) primitives, which implement the WAI-ARIA patterns — keyboard interaction, focus management, and ARIA wiring — so the hard parts are handled. This page covers what you get for free and the few things that remain your responsibility. ## What's handled for you [#whats-handled-for-you] * **Keyboard interaction** — menus, listboxes, tabs, sliders, and dialogs implement the expected arrow-key, `Home`/`End`, `Escape`, and typeahead behavior, including roving `tabindex` within groups. * **Focus management** — overlays (Dialog, Drawer, Popover) trap focus while open and restore it to the trigger on close. * **ARIA wiring** — roles, `aria-expanded`, `aria-controls`, `aria-selected`, and label/description associations are set on the right elements. * **Reduced motion** — every animation is reduced-motion aware (via `motion-reduce:transition-none` or `motion-safe:`), so motion is skipped for users who ask for it. See [Animation](/ui/docs/react/animation). ## Focus visibility [#focus-visibility] A focus ring is applied globally on `:focus-visible` (keyboard focus), using the ring tokens from [Colors](/ui/docs/react/colors#focus-rings). It stays out of the way for mouse users and appears for keyboard users. Keep it intact — don't strip outlines from interactive elements. ## Labeling controls [#labeling-controls] Associate a visible label with a single control using `Field` and `FieldLabel`, which wire up the `for`/`id` relationship automatically: ```tsx import { Field, FieldLabel } from '@appica/ui-react/field' import { Input } from '@appica/ui-react/input' export default function EmailField() { return ( Email ) } ``` `FieldLabel` renders a ` ```tsx Left Center ``` Controls with no visible label (an icon-only button, a search field) still need an accessible name — give them `aria-label`: ```tsx } /> Helpful hint ) } ``` ## Links that look like a button [#links-that-look-like-a-button] Sometimes you don't want to *change* a component's element — you want a plain link (or other element) to simply wear a component's styling. Don't reach for `render` here: rendering an `` through `Button` forces button semantics onto a link. Instead, apply the exported `buttonVariants` classes directly to the element that already has the right semantics: ```tsx import { buttonVariants } from '@appica/ui-react/button' // A real link that looks like a primary button. Read the docs // Works with a router Link too. View pricing ``` Links and buttons have different, non-interchangeable semantics: a link navigates, a button performs an action. The `render` prop is for adopting a *different element while keeping the component's role* — not for repainting a link as a button. Styling the `` with `buttonVariants` keeps link semantics intact and gives you the identical look. `buttonVariants` isn't the only one — several components export their variant recipe for exactly this purpose: | Helper | Import from | Style any element like | | ------------------------ | ----------------------------- | ---------------------------------- | | `buttonVariants` | `@appica/ui-react/button` | Button (`variant`, `size`) | | `inputVariants` | `@appica/ui-react/input` | Input (`variant`, `size`, `state`) | | `navigationLinkVariants` | `@appica/ui-react/navigation` | NavigationLink (`variant`, `size`) | Each takes the same variant props as its component and returns a className string, so they drop into any element and compose with your own classes through `cn` or a template literal. ## Where to put `className` [#where-to-put-classname] This is the one rule worth memorizing. When composing a render-prop wrapper: * **Visual overrides** (`className`, `style`) go on the **wrapper**. * **Structural/behavioral props** (`href`, `type`, `variant`, `size`, rendering as a different element) go on the JSX inside `render`. ```tsx // ✅ Do — className on the wrapper Help} /> // ❌ Don't — className on the render JSX Help} /> ``` Putting `className` on the inner JSX can cause a **React hydration mismatch** in Server Components: the styled element is built on the server, serialized across the boundary, then cloned on the client, and the final class strings can disagree. Keeping `className` on the wrapper avoids it — and class precedence still works, since the wrapper's classes are merged last and win on conflicts. If you specifically need a class on the inner element, extract the trigger into its own `'use client'` component rather than promoting the whole page to client. ## Class merging [#class-merging] Overrides are merged with [tailwind-merge](https://github.com/dcastil/tailwind-merge), so conflicting utilities are de-duplicated and the last one wins. You don't need to fight specificity: ```tsx // `rounded-full` overrides the component's default radius cleanly. ``` # Dark Mode (/ui/docs/react/dark-mode) Appica UI is dark-mode-ready out of the box. The token system ships both a light and a dark palette; switching themes is a matter of toggling a class on the `` element, which [`ThemeProvider`](/ui/docs/react/theme-provider) manages for you — including persistence and a no-flash first paint. ## How it works [#how-it-works] Dark mode is **class-based**. When the `dark` class is on a `` ancestor, the dark token values apply. The library defines the variant as: ```css @custom-variant dark (&:is(.dark *)); ``` So you can write dark-only utilities in your own markup too: ```tsx
``` ## Setup [#setup] Wrap your app in `ThemeProvider` at your app's root (covered in [Installation](/ui/docs/react/installation)). It applies the correct class before the page paints, so there's no flash of the wrong theme on load. The example uses Next.js's root layout — see the [Framework notes](/ui/docs/react/installation#framework-notes) for where this file lives in Vite, React Router / Remix, TanStack Start, and Astro: ```tsx title="app/layout.tsx (Next.js)" import { ThemeProvider } from '@appica/ui-react/providers/theme-provider' export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` `suppressHydrationWarning` on `` is expected: the no-flash script sets the theme class before React hydrates, so the server and client markup intentionally differ by that one attribute. By default `ThemeProvider` follows the OS preference (`enableSystem`) and persists the user's choice under the `theme` key in `localStorage`. ## Building a theme toggle [#building-a-theme-toggle] Read and set the theme with [`useTheme`](/ui/docs/react/use-theme). Guard theme-dependent UI with `mounted` so the server-rendered output matches the first client render: ```tsx title="theme-toggle.tsx" 'use client' import { useTheme } from '@appica/ui-react/hooks/use-theme' import { Button } from '@appica/ui-react/button' import { SunHigh, MoonStars } from '@appica/icons-react' export function ThemeToggle() { const { resolvedTheme, setTheme, mounted } = useTheme() // Avoid rendering theme-specific content until mounted. if (!mounted) return ) } ``` * `theme` is the stored choice, which may be `'system'`. * `resolvedTheme` is what's actually applied — `'system'` resolved to `'light'` or `'dark'`. * `systemTheme` is the current OS preference. ## Common options [#common-options] `ThemeProvider` accepts several props to tune behavior: | Prop | Default | Description | | --------------------------- | ------------------------------ | ------------------------------------------------- | | `defaultTheme` | `'system'` (if `enableSystem`) | Theme used before the user makes a choice | | `enableSystem` | `true` | Follow the OS `prefers-color-scheme` | | `storageKey` | `'theme'` | `localStorage` key for the persisted choice | | `forcedTheme` | — | Pin a theme for a subtree (e.g. a marketing page) | | `disableTransitionOnChange` | `false` | Suppress CSS transitions during the switch | | `enableColorScheme` | `true` | Set `color-scheme` so native controls match | To force a single theme on a specific route, nest another provider with `forcedTheme`: ```tsx ``` See [`ThemeProvider`](/ui/docs/react/theme-provider) for the full prop reference and [`useTheme`](/ui/docs/react/use-theme) for the hook. # Direction Provider (/ui/docs/react/direction-provider) `DirectionProvider` sets the reading direction for descendant components. It's a thin wrapper over [Base UI](https://base-ui.com)'s direction provider, so your app depends on `@appica/ui-react` rather than the underlying primitive. ```tsx import { DirectionProvider } from '@appica/ui-react/providers/direction-provider' ``` ## Usage [#usage] Wrap your app at its root and set `dir`. For full RTL support, also set the `dir` attribute on `` so CSS logical properties and native text flow resolve correctly — see [Right-to-Left (RTL)](/ui/docs/react/rtl). The example uses Next.js's root layout; for the equivalent root file in other frameworks, see the [Framework notes](/ui/docs/react/installation#framework-notes) in Installation. ```tsx title="app/layout.tsx (Next.js)" {children} ``` Descendant components read the value via [`useDirection`](/ui/docs/react/use-direction). Components that position elements with JavaScript — Slider, Switch, Carousel, and overlays like menus — rely on it to know which side is "end." Optional. With no provider, components default to `ltr`. Reach for it only when supporting RTL. ## Props [#props] | Prop | Type | Default | Description | | ---------- | ---------------- | ------- | ------------------------------------------- | | `dir` | `'ltr' \| 'rtl'` | `'ltr'` | Reading direction for descendant components | | `children` | `ReactNode` | — | The subtree that should read this direction | # Fonts (/ui/docs/react/fonts) Typography in Appica UI is driven by two design tokens — `--font-sans` and `--font-mono` — exactly like colors and radii. Switching fonts is a two-part job: **load** the font in your app, then **point the token at it**. The first part is framework-specific; the second is one line of CSS that's the same everywhere. ## The default fonts [#the-default-fonts] Out of the box, `@appica/ui-react/styles.css` sets both tokens to native system font stacks: ```css :root { --font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; } ``` System fonts download nothing, render instantly, and never cause layout shift — a good default. They feed Tailwind's `font-sans` / `font-mono` utilities through the `@theme inline` block, and Tailwind's preflight applies `--font-sans` as the base family for the whole document. So **overriding the raw `--font-sans` token re-fonts your entire UI** — every component included. Override the raw `--font-sans` token at `:root`, not its alias inside the `@theme inline` block. This mirrors how [Theming](/ui/docs/react/theming) treats colors and radii — the theme layer is derived, so the raw token is the one to redefine. ## The one line that always applies [#the-one-line-that-always-applies] Whatever loads the font, you finish by redefining the raw token after the import, keeping a fallback stack so text stays readable while the web font loads: ```css title="globals.css" @import 'tailwindcss'; @import '@appica/ui-react/styles.css'; @source '@appica/ui-react'; :root { --font-sans: 'Geist', ui-sans-serif, system-ui, sans-serif; } ``` The font name on the left of the fallback list must match whatever the loading step below makes available — a `font-family` from `@font-face`, an `@fontsource` package, or the CSS variable `next/font` generates. The rest of this guide is just the different ways to get that name onto the page. ## Loading the font [#loading-the-font] How you load a font depends on your framework. Next.js has a first-class font pipeline (`next/font`); everywhere else you self-host with [Fontsource](https://fontsource.org) or a plain `@font-face`. All of these self-host the files, which is faster and more private than linking to a third-party CDN. ### Next.js — `next/font` [#nextjs--nextfont] `next/font` downloads the font at build time, self-hosts it, and hands you a CSS variable with zero layout shift. Use `next/font/google` for a Google font: ```tsx title="app/layout.tsx" import { Geist } from 'next/font/google' const geist = Geist({ subsets: ['latin'], variable: '--font-geist', display: 'swap', }) export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` …or `next/font/local` for font files you ship yourself: ```tsx title="app/layout.tsx" import localFont from 'next/font/local' const brand = localFont({ src: [ { path: './fonts/Brand-Regular.woff2', weight: '400', style: 'normal' }, { path: './fonts/Brand-Bold.woff2', weight: '700', style: 'normal' }, ], variable: '--font-brand', display: 'swap', }) ``` Either way, map the generated variable onto the Appica token: ```css title="app/globals.css" :root { --font-sans: var(--font-geist), ui-sans-serif, system-ui, sans-serif; } ``` `next/font` only exposes `--font-geist` on the element you put `geist.variable` on (and its descendants), so apply it to ``. Don't also spread `geist.className` onto `` — that hardcodes the font on the body and bypasses the token, so component-level `font-mono` and any later override stop working. Pick the variable approach and let `--font-sans` flow through. ### Vite, TanStack Start, React Router 7 / Remix — Fontsource [#vite-tanstack-start-react-router-7--remix--fontsource] These bundlers have no built-in font helper, so self-host with Fontsource — one npm package per family, imported once: ```ts title="src/main.tsx" import '@fontsource-variable/geist' ``` ```css title="src/index.css" :root { --font-sans: 'Geist Variable', ui-sans-serif, system-ui, sans-serif; } ``` The `font-family` Fontsource registers is shown on each font's page — variable fonts use the `… Variable` suffix as above; static weights use the bare name (`'Geist'`). ### Any framework — local files with `@font-face` [#any-framework--local-files-with-font-face] If you'd rather not add a dependency, declare the face yourself and drop the files in your `public/` (or `static/`) folder. This works in every framework, including Astro: ```css title="globals.css" @font-face { font-family: 'Brand Sans'; src: url('/fonts/brand-sans.woff2') format('woff2'); font-weight: 100 900; /* a single variable file */ font-display: swap; } :root { --font-sans: 'Brand Sans', ui-sans-serif, system-ui, sans-serif; } ``` You *can* load a font with Google's `` snippet and it works anywhere, but it adds a third-party request, an extra DNS round-trip, and sends your visitors' IPs to Google. `next/font`, Fontsource, and `@font-face` all serve the files from your own origin — faster and privacy-friendly. Reach for the link tag only as a quick prototype. ## The monospace font [#the-monospace-font] `--font-mono` works identically — load the font, then redefine the token. It feeds `font-mono`, code blocks, and anything that opts into monospace: ```css :root { --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; } ``` ## Framework notes [#framework-notes] The CSS override is identical everywhere; only the loading mechanism and where it lives change: | Framework | How to load | Where | | ----------------------------------------------------------------------------------------------------------- | ---------------------------------- | --------------------------------- | | Next.js (App Router) | `next/font` → CSS variable | `app/layout.tsx` + `globals.css` | | Vite / CRA | Fontsource import or `@font-face` | `src/main.tsx` + `src/index.css` | | TanStack Start | Fontsource import or `@font-face` | root route + `src/styles/app.css` | | React Router 7 / Remix | Fontsource import or `@font-face` | `app/root.tsx` + `app/app.css` | | Astro | `@font-face` (or Astro's font API) | `src/styles/global.css` | Whatever the source, the contract is the same: make a `font-family` name resolvable on the page, then put it first in `--font-sans` / `--font-mono` with the original stack trailing as a fallback. ## Next steps [#next-steps] * [Theming](/ui/docs/react/theming) — the token system fonts plug into. * [Colors](/ui/docs/react/colors) — recolor with the same override pattern. * [Dark Mode](/ui/docs/react/dark-mode) — switch and persist themes. # Forms & Validation (/ui/docs/react/forms) Forms are built from two pieces: `Field`, which groups a control with its label, description, and error message and wires up the accessibility relationships; and `Form`, which coordinates the fields and surfaces server-side errors. Both wrap [Base UI](https://base-ui.com) primitives. ## Anatomy of a field [#anatomy-of-a-field] A `Field` composes a label, a control, an optional description, and an error slot. The `for`/`id` and `aria-describedby` associations are handled for you. ```tsx import { Field, FieldLabel, FieldDescription, FieldError } from '@appica/ui-react/field' import { Input } from '@appica/ui-react/input' export default function EmailField() { return ( Email We'll only use this to send receipts. ) } ``` ## Validation [#validation] ### Native constraints [#native-constraints] Standard HTML attributes — `required`, `type`, `min`, `maxLength`, `pattern` — are validated automatically. Point `FieldError` at a specific failure with `match`, or render the browser's default message with a bare ``: ```tsx Email An email is required. Enter a valid email address. ``` ### Custom validation [#custom-validation] For rules HTML can't express, pass a `validate` function to the `Field`. Return a string (or array of strings) to fail, or `null` to pass. Control when it runs with `validationMode` (`'onBlur'` or `'onChange'`): ```tsx (String(value).length < 3 ? 'At least 3 characters.' : null)} > Username ``` `validate` can be async — return a promise — which is handy for availability checks against an API. ### Shake on invalid [#shake-on-invalid] For an extra cue when validation fails, hang the `animate-shake` utility off the control's invalid state. Base UI sets `data-invalid` on the control when the field fails validation, so put the class on the `Input` to shake just the control: ```tsx Email ``` The shake plays once, when the field *becomes* invalid (the moment `data-invalid` is added) — not on every failed submit while it stays invalid. Gating it behind `motion-safe:` keeps it off for users who prefer reduced motion, per [Animation](/ui/docs/react/animation). ## Grouping with Fieldset [#grouping-with-fieldset] Use `Fieldset` and `FieldsetLegend` to group related fields under a shared label (an accessible `
`/``): ```tsx import { Fieldset, FieldsetLegend } from '@appica/ui-react/fieldset' export default function BillingAddress() { return (
Billing address Street City
) } ``` ## The Form wrapper [#the-form-wrapper] `Form` ties the fields together and handles submission. Its most useful job is displaying **server-side** errors: return them keyed by field `name` and pass them to `errors`, and each `Field` shows its message in place. ```tsx 'use client' import * as React from 'react' import { Form } from '@appica/ui-react/form' import { Field, FieldLabel, FieldError } from '@appica/ui-react/field' import { Input } from '@appica/ui-react/input' import { Button } from '@appica/ui-react/button' export default function SignupForm() { const [errors, setErrors] = React.useState({}) return (
{ event.preventDefault() const data = new FormData(event.currentTarget) const result = await signUp(data) // your server action / API call if (result.errors) setErrors(result.errors) // e.g. { email: 'Already taken.' } }} > Email
) } ``` Inputs are uncontrolled by default — read values from `FormData` on submit, as above. You can still control any field with `value`/`onValueChange` (or `onChange`) when you need to. ## Accessibility [#accessibility] Labels, descriptions, and error messages are associated with their control automatically, and invalid fields are marked with `aria-invalid`. See [Accessibility](/ui/docs/react/accessibility) — and note that grouped controls (`RadioGroup`, `CheckboxGroup`) are labeled with `aria-label`/`aria-labelledby`, not `FieldLabel`. # Installation (/ui/docs/react/installation) Appica UI ships as a single tree-shakeable package. Components are built on [Base UI](https://base-ui.com) primitives and styled with Tailwind CSS v4 design tokens, so installation is two steps: add the package, then point Tailwind at it. ## Prerequisites [#prerequisites] Appica UI targets modern React and Tailwind. Make sure your project has: | Dependency | Version | | ------------ | -------- | | React | `>= 19` | | React DOM | `>= 19` | | Tailwind CSS | `>= 4.0` | **React 19 is a hard requirement:** components use the modern ref-as-prop API and other React 19 patterns, with no `forwardRef` shims. If you're on React 18, upgrade before installing. Appica UI doesn't bundle Tailwind — it relies on *your* project's Tailwind to compile the component styles. Before installing, set up Tailwind v4 in your app: install `tailwindcss` **and** its build plugin (`@tailwindcss/vite` for Vite, `@tailwindcss/postcss` for PostCSS/Next.js), then confirm that `@import 'tailwindcss';` in your global stylesheet generates utilities. If Tailwind isn't compiling your own classes yet, Appica UI's components will render unstyled. ## Install [#install] ## Configure Tailwind [#configure-tailwind] Import the design tokens after Tailwind in your global stylesheet, and add a `@source` directive so Tailwind scans the compiled library for class names. ```css title="globals.css" @import 'tailwindcss'; @import '@appica/ui-react/styles.css'; @source '../node_modules/@appica/ui-react/dist'; ``` Tailwind ignores `node_modules` by default. Without the `@source` directive it won't generate the utility classes used inside the compiled components, and they'll render unstyled. `@source` takes a path or glob **relative to the stylesheet that contains it**, not a bare package name — `@source '@appica/ui-react'` does not resolve and silently scans nothing. Count the `../` needed to reach `node_modules` from your CSS file: a stylesheet one level deep (`app/globals.css`, `src/index.css`) needs a single `../`; a deeper one (`src/styles/app.css`) needs `../../`. This matches Tailwind's own [`@source` documentation](https://tailwindcss.com/docs/functions-and-directives#source-directive), whose example is `@source '../node_modules/@my-company/ui-lib';`. The `styles.css` import brings in the full token system — colors, radii, shadows, typography, and the `light`/`dark` themes. See [Theming](/ui/docs/react/theming) to customize it. ### Why there's no prebuilt CSS to import [#why-theres-no-prebuilt-css-to-import] Appica UI ships its components' class names as source for *your* Tailwind to compile, rather than a prebuilt stylesheet you drop in. That extra `@source` line buys a lot: * **One deduplicated build.** The utilities the components use are generated into the same pass as your own, so shared classes (`flex`, `rounded-lg`, `px-4`…) are emitted once instead of shipping twice. * **Only what you use.** Tailwind tree-shakes across your app and the library together — utilities for components you never import are never generated. * **Your tokens win.** Because the classes are compiled against *your* Tailwind config, overrides to the design tokens in [Theming](/ui/docs/react/theming) — colors, radii, spacing, fonts — flow into the components automatically. A prebuilt stylesheet would freeze those values at publish time. * **No version-locked CSS.** There's no compiled stylesheet to drift out of sync with your Tailwind version; the components restyle themselves against whatever Tailwind v4 you're on. ## Add the provider [#add-the-provider] Wrap your app in `ThemeProvider` to enable theming and dark mode. It injects a small inline script that applies the stored theme before first paint, so there's no flash of the wrong theme. In Next.js that's the root layout's ``; in a Vite/CRA app, wrap your root the same way. ```tsx title="app/layout.tsx (Next.js)" import { ThemeProvider } from '@appica/ui-react/providers/theme-provider' export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` That's all you need to start. See [Theme Provider](/ui/docs/react/theme-provider) for the full API — custom themes, forcing a theme, and flash-prevention details — and the optional [`DirectionProvider`](/ui/docs/react/direction-provider) (RTL) and [`ReducedMotionProvider`](/ui/docs/react/reduced-motion-provider), which you add only when you need them. ## Framework notes [#framework-notes] Appica UI is a plain package, so the steps above are the same everywhere — unlike a file-scaffolding CLI, there's no per-framework setup to learn. The only things that move are which Tailwind build plugin you install, where your global stylesheet lives, and where the provider goes: | Framework | Tailwind plugin | Global stylesheet | Wrap the provider | | ----------------------------------------------------------------------------------------------------------- | ---------------------- | ----------------------- | ---------------------------- | | Next.js (App Router) | `@tailwindcss/postcss` | `app/globals.css` | root layout `` | | Vite / CRA | `@tailwindcss/vite` | `src/index.css` | root component in `main.tsx` | | TanStack Start | `@tailwindcss/vite` | `src/styles/app.css` | root route component | | React Router 7 / Remix | `@tailwindcss/vite` | `app/app.css` | root component in `root.tsx` | | Astro | `@tailwindcss/vite` | `src/styles/global.css` | see note below | Everything else — the `@import`s and the subpath imports — is identical regardless of framework. The one value that tracks the stylesheet is the `@source` path: count the `../` needed to reach `node_modules` from wherever your global CSS lives (see [Configure Tailwind](#configure-tailwind) above). Astro renders React as [islands](https://docs.astro.build/en/concepts/islands/), not a single tree, so it needs a little extra wiring. Add the `@astrojs/react` integration first, then mount anything interactive with a client directive (e.g. `client:load`) — components without one ship zero JS and won't react to theme changes. Because each island is its own React root, a `ThemeProvider` placed in an `.astro` layout **won't** share context across islands; either render an interactive section as one island wrapped in the provider, or apply the theme class in your `.astro` `` and use the provider only inside hydrated islands. ## Use a component [#use-a-component] Import components from their subpath for the smallest bundle, and you're ready: ```tsx import { Button } from '@appica/ui-react/button' export default function App() { return } ``` ## Next steps [#next-steps] * [Usage](/ui/docs/react/usage) — import conventions, variants, and composition basics. * [Theming](/ui/docs/react/theming) — customize colors, radii, and tokens. * [Dark Mode](/ui/docs/react/dark-mode) — build a theme toggle. * [Components](/ui/components/react/button) — browse the full catalog. # Reduced Motion Provider (/ui/docs/react/reduced-motion-provider) `ReducedMotionProvider` opts a subtree out of animation. Components already honor the OS `prefers-reduced-motion` setting on their own — this provider is the explicit, global override (for a user preference toggle, demos, or tests). ```tsx import { ReducedMotionProvider } from '@appica/ui-react/providers/reduced-motion-provider' ``` ## Usage [#usage] ```tsx ``` When `disableAnimations` is set, the provider writes a `data-disable-animations` attribute to ``. That's deliberate: it has to reach Base UI's portaled popups, which render under `document.body` and so escape the React subtree. ## Two channels [#two-channels] The provider drives motion through two coordinated channels: * **CSS** — the `data-disable-animations` attribute on `` triggers the `motion-reduce` variant page-wide (popups included). This is what stops component transitions. See [Animation](/ui/docs/react/animation). * **JavaScript** — [`useReducedMotion`](/ui/docs/react/use-reduced-motion), scoped to this provider's React subtree, returns `true` so your own animation code can branch. ## Props [#props] | Prop | Type | Default | Description | | ------------------- | ----------- | ------- | -------------------------------------------------------- | | `disableAnimations` | `boolean` | `false` | Force-disable animations regardless of the OS preference | | `children` | `ReactNode` | — | The subtree to opt out of animation | Leaving `disableAnimations` off doesn't re-enable motion for users who request reduced motion at the OS level — that preference is always respected. The provider can only make things *more* still, never less. # Right-to-Left (RTL) (/ui/docs/react/rtl) Appica UI supports right-to-left layouts. Most mirroring happens automatically through CSS logical properties; a few components that compute layout in JavaScript read the direction from [`DirectionProvider`](/ui/docs/react/direction-provider). ## Two things to set [#two-things-to-set] For full RTL support, set the direction in **two** places: **1. The `dir` attribute** on ``, so the browser mirrors text flow and logical CSS resolves correctly: ```html ``` **2. `DirectionProvider`**, so components that position elements with JavaScript (Slider, Switch, Carousel, menus, and other floating surfaces) know which way is "end": ```tsx title="app/layout.tsx (Next.js)" import { DirectionProvider } from '@appica/ui-react/providers/direction-provider' export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` `DirectionProvider` defaults to `ltr`, so you only need it when supporting RTL. With no provider, components behave left-to-right. The example uses Next.js's root layout; for the equivalent root file in Vite, React Router / Remix, TanStack Start, and Astro, see the [Framework notes](/ui/docs/react/installation#framework-notes) in Installation. ## Why most things "just work" [#why-most-things-just-work] Appica UI styles with **logical** properties rather than physical ones — padding and margins use `ps`/`pe` and `ms`/`me` (inline-start/end) instead of left/right, and positioning uses `start`/`end`. These resolve against the document direction, so a component laid out for LTR mirrors for RTL without any extra work. Write your own RTL-ready markup the same way: ```tsx // ✅ Mirrors automatically
// ❌ Stays put in RTL
``` ## Floating popups in a scoped RTL region [#floating-popups-in-a-scoped-rtl-region] Menus, selects, popovers, and other floating surfaces render in a **portal** at the end of ``. Their `start` / `end` **alignment** mirrors based on the direction the portalled element inherits from the document — read from CSS by the positioning engine, **not** from `DirectionProvider`. With `dir="rtl"` on `` (as above) this is automatic, so you never think about it. It only matters when you scope RTL to a **subtree** of an otherwise-LTR page. The popup escapes that subtree to the (LTR) ``, so its alignment stays left-to-right even though the submenu *side* still follows `DirectionProvider`. Give the popup's positioner the direction explicitly, or portal it into your RTL container: ```tsx // Set the direction on the positioner… // …or render the popup inside your RTL container instead of ``` Prefer setting `dir="rtl"` on `` whenever the whole document is RTL — then every floating popup mirrors with no per-component setup. The positioner override above is only for mixed LTR/RTL pages. ## Reading the direction in code [#reading-the-direction-in-code] When your own component needs to branch on direction, read it with [`useDirection`](/ui/docs/react/use-direction): ```tsx 'use client' import { useDirection } from '@appica/ui-react/hooks/use-direction' function NextIcon() { const dir = useDirection() return {dir === 'rtl' ? '←' : '→'} } ``` ## Switching at runtime [#switching-at-runtime] To toggle direction without a full reload, update both the `` attribute and the `DirectionProvider` value together so the CSS and JS channels stay in sync. See [`DirectionProvider`](/ui/docs/react/direction-provider) and [`useDirection`](/ui/docs/react/use-direction) for the full reference. # Theme Provider (/ui/docs/react/theme-provider) `ThemeProvider` owns the active theme. It applies the theme class to ``, persists the choice to storage, optionally follows the OS preference, and injects an inline script that sets the theme **before first paint** so there's no flash. ```tsx import { ThemeProvider } from '@appica/ui-react/providers/theme-provider' ``` ## Usage [#usage] Wrap your app once, as high as possible — at your app's root, inside ``. The example below uses Next.js's root layout; for the equivalent file in Vite, React Router / Remix, TanStack Start, and Astro, see the [Framework notes](/ui/docs/react/installation#framework-notes) in Installation. ```tsx title="app/layout.tsx (Next.js)" export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` Read and change the theme anywhere below it with [`useTheme`](/ui/docs/react/use-theme). For building a toggle, see [Dark Mode](/ui/docs/react/dark-mode). Nesting `ThemeProvider` is safe — inner providers are a passthrough. Only the outermost one owns state, so you can use a nested provider purely to set `forcedTheme` on a subtree. ## Props [#props] | Prop | Type | Default | Description | | --------------------------- | ------------------------ | ---------------------------- | ------------------------------------------------------------ | | `themes` | `string[]` | `['light', 'dark']` | Available theme names | | `defaultTheme` | `string` | `'system'` if `enableSystem` | Theme used before a choice is stored | | `forcedTheme` | `string` | — | Force a theme for the whole subtree (overrides storage + OS) | | `enableSystem` | `boolean` | `true` | Respect the OS `prefers-color-scheme` | | `enableColorScheme` | `boolean` | `true` | Set `color-scheme` on `` so native UI matches | | `disableTransitionOnChange` | `boolean` | `false` | Suppress CSS transitions during a theme switch | | `storageKey` | `string` | `'theme'` | `localStorage` key for the persisted choice | | `value` | `Record` | — | Map a theme name to a custom class applied on `` | | `nonce` | `string` | — | CSP nonce forwarded to the inline script | | `scriptProps` | `ScriptHTMLAttributes` | — | Extra props for the inline `