Layout

Theme

The Motion root: applies fonts, tokens, base styles and the light/dark appearance to everything inside, including portalled menus and dialogs.

appearance: light · showing: light
appearance: dark · showing: dark

Usage

Use it to:

  • wrap the whole app once (on <body> with asChild)
  • force a section into the other appearance (a nested <Theme appearance="dark">); its overlays follow it
  • portal your own floating UI with <ThemePortal> so it keeps the Theme’s appearance

Don’t use it to:

  • styling outside Motion components (Motion styles only apply inside a Theme and its overlays)
  • raw createPortal or a Radix *.Portal to <body> for Motion surfaces (they lose the Theme’s appearance)

Installation

Install the package (React 19):

terminal
npm install @krackedui/motion

Import the styles once, in your app entry or root layout:

/src/main.tsx
import '@krackedui/motion/styles.css';

Use it anywhere inside your app's <Theme> (see Installation):

/src/ThemeExample.tsx
import '@krackedui/motion/styles.css';
import { Button, Theme } from '@krackedui/motion';

export function App() {
  return (
    <Theme appearance="system">
      <Button variant="primary">Save</Button>
    </Theme>
  );
}

Examples

Appearance

light, dark, or system (follows the OS; renders light on the server, then updates after mount).

appearance: light · showing: light
appearance: dark · showing: dark
appearance: system · showing: light

Mixed themes

Light and dark Themes can share a page. Menus, popovers, selects, tooltips and dialogs open in the appearance of the Theme they belong to, not of the page.

Next.js root layout

Use asChild to make <body> the root, so portalled overlays sit inside it.

appearance: system · showing: light

Reading the theme

appearance: system · showing: light

Your own floating UI

ThemePortal is the portal every Motion overlay uses. Wrap custom floating elements in it; pass primitive={X.Portal} when rendering a Radix primitive yourself.

appearance: dark · showing: dark

API Reference

Theme

A <div> (or your element with asChild) with the motion-root class; supports all <div> props. Adds:

PropTypeDefault
appearance
"light""dark""system"
"light"
asChild
boolean
false

ThemePortal

Renders its children into the nearest Theme’s portal layer: a container on <body> carrying that Theme’s light/dark class and custom properties. Outside a Theme it renders into <body>.

PropTypeDefault
children
ReactNode
primitive
PortalPrimitive
forceMount
true
container
Element | DocumentFragment | null

useTheme()

The nearest Theme’s appearance. Throws outside a <Theme>.

PropTypeDefault
appearance
"light""dark""system"
resolvedAppearance
"light""dark"

Accessibility

  • Sets color-scheme, so native controls and scrollbars match the theme.
  • Every text token pair meets WCAG AA (4.5:1) in both themes; system respects the user’s OS preference.
  • Theme adds no landmarks or roles; use asChild on <body> or <main> when you want its semantics.
  • Overlays render in the Theme’s portal layer at the end of <body>: focus is still trapped in dialogs and returned to the trigger on close, and modal dialogs hide the rest of the page from assistive technology as usual.
  • Each overlay keeps the contrast of the Theme it opened from, so a light Theme’s menu never turns dark because a dark section is on the page.
© 2026 Motion. Internal documentation.