# Theming and styling channels (/cloud/foundations/theming)



`@nyte-ai/ui` divides styling responsibility three ways, and each primitive exposes exactly the
channels that division needs.

| Layer         | Owns                                             | Mechanism                                                          |
| ------------- | ------------------------------------------------ | ------------------------------------------------------------------ |
| Base UI       | Interaction, focus, ARIA, data attributes        | Headless components                                                |
| `@nyte-ai/ui` | Reusable component styling                       | StyleX `stylex.create` reading [tokens](/cloud/foundations/tokens) |
| The app       | Layout, typography, product identity, composites | Token overrides, `className`, `xstyle`, own components             |

## Token overrides [#token-overrides]

Override on an ancestor. The app root is the right place; a preview container is also fine, and
that is how the examples on this site are themed independently of the docs chrome.

```css
.my-app {
  color-scheme: dark;
  --nyte-color-avatar-orange-background: #5f2a06;
  --nyte-color-avatar-orange-foreground: #ffb27d;
}
```

Override only the tokens that carry identity. Overriding `--nyte-control-height-md` to fix one
layout is a layout problem; solve it with `className`.

## The three props every styled primitive accepts [#the-three-props-every-styled-primitive-accepts]

Every component in `packages/ui/src/components/ui` accepts these and merges them in this order.

| Prop        | Type                  | Purpose                                                                                                          |
| ----------- | --------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `className` | `string`              | Consumer layout, including Tailwind utilities. Appended after the StyleX class.                                  |
| `style`     | `React.CSSProperties` | Inline overrides. Spread after StyleX's inline style, so it wins.                                                |
| `xstyle`    | `XStyle`              | A deliberate StyleX override. Passed as the last argument to `stylex.props`, so it wins by StyleX's merge rules. |

`XStyle` is `stylex.StyleXArray` of compiled styles, booleans, and null; you can pass one style,
an array, or a conditional. The merge itself is `mergeStyleProps` in `packages/ui/src/style.ts`:

```ts
export function mergeStyleProps(base, className, style) {
  return {
    className: [base.className, className].filter(Boolean).join(" ") || undefined,
    style: { ...base.style, ...style },
  };
}
```

`StyledProps<P>` is the helper type that strips `className`/`style` from a Base UI props type and
adds the three channels back. Use it when wrapping a new part.

## `unstyled` [#unstyled]

`Button`, `Input`, and `Textarea` accept `unstyled`. When true the component renders the Base UI
part with no StyleX class at all, keeping only the interaction layer and the `data-slot`
attribute. On `Button`, `unstyled: true` also forbids `variant` and `size` at the type level.

`unstyled` is for product surfaces that supply a complete `xstyle` treatment of their own. It is
not for "I need a slightly different button"; that is a token override or `xstyle`.

## Data attributes [#data-attributes]

Every primitive sets `data-slot="<part>"` so a consumer can target a part from CSS without knowing
the StyleX class. Variant-bearing primitives also expose their appearance:

| Component           | Attributes                                                                  |
| ------------------- | --------------------------------------------------------------------------- |
| `Button`            | `data-slot="button"`, `data-variant`, `data-size` (omitted when `unstyled`) |
| `Avatar`            | `data-slot="avatar"`, `data-size`, `data-tone`                              |
| `AvatarImage`       | `data-slot="avatar-image"`                                                  |
| `AvatarFallback`    | `data-slot="avatar-fallback"`                                               |
| `Input`             | `data-slot="input"`                                                         |
| `Textarea`          | `data-slot="textarea"`                                                      |
| `DropdownMenuItem`  | `data-slot="dropdown-menu-item"`, `data-variant`, `data-inset`              |
| `DropdownMenuLabel` | `data-inset`                                                                |

Base UI adds its own state attributes on top (`data-disabled`, `data-popup-open`,
`data-highlighted`, `data-checked`, …). Those are listed on each component page.

## Headless subpaths [#headless-subpaths]

For anything without a styled wrapper, import the Base UI namespace from its subpath and style it
in the app:

```ts
import { Popover } from "@nyte-ai/ui/popover";
```

Import from the subpath, not from `@base-ui/react`, so the Base UI version stays behind the shared
package. See [Headless](/cloud/headless).

## Dark mode [#dark-mode]

Tokens use `light-dark()`, so a primitive follows whichever `color-scheme` is in effect. There is
no `.dark` class in the primitives. `@nyte-ai/ui/styles.css` does declare
`@custom-variant dark (&:is(.dark *))` for Tailwind consumers that want class-based variants in
their own utilities; it does not affect the primitives.
