---
title: "Theming"
description: "Customize component styles with the ui prop, app.config, and per-slot theme overrides."
canonical_url: "https://nuxt-motion-block.pages.dev/getting-started/theming"
---
# Theming

> Customize component styles with the ui prop, app.config, and per-slot theme overrides.

Every Nuxt Motion Block component is built on a [tailwind-variants](https://www.tailwind-variants.org) theme, the same technique Nuxt UI uses for its own components. Each theme declares a set of named **slots** — `root`, `header`, `content`, and so on — and every slot holds the default utility classes for that part of the markup.

You never have to touch the theme file to restyle a component. There are three override layers, and they all target the same slot names.

## The `ui` prop

The `ui` prop takes an object keyed by slot name. Only the slots you name are changed; the rest keep their defaults.

```vue
<template>
  <MSpotlight
    :ui="{
      root: 'size-80 bg-primary-500/30'
    }"
  />
</template>
```

Slot classes are merged with conflict resolution, so a utility you pass replaces the matching default instead of both being emitted. The same merge applies to the `class` prop, which targets the component's primary slot. Use `class` for quick tweaks and `ui` when you need to reach a specific inner element.

## Global configuration with `app.config`

To restyle a component everywhere in your app, add it to `app.config.ts` under `ui.<componentName>`. The overrides live under a `slots` key, mirroring the theme's own shape:

```ts [app.config.ts]
export default defineAppConfig({
  ui: {
    spotlight: {
      slots: {
        root: 'bg-primary-500/30'
      }
    }
  }
})
```

The `<componentName>` is the component's camelCase name — `spotlight`, `magneticButton`, `dynamicIsland`, `codeBlock`, and so on. Each component page lists its slots and default classes at the bottom, so you can see exactly which keys are available.

## Merge priority

Overrides are applied in this order, from lowest to highest:

1. The component's theme defaults
2. `app.config.ts` (`ui.<componentName>.slots`)
3. The `ui` prop
4. The `class` prop

Because `class` sits on top, it is the right tool for one-off instances, while `app.config` is the place for app-wide brand decisions.

## Authoring your own components

If you build a component that should follow the same contract, use the [`useMotionBlockUI`](https://nuxt-motion-block.pages.dev/composables/use-motion-block-ui) composable. It merges a `tailwind-variants` theme with the user's `app.config` overrides and returns a `tv` instance you can call per slot.

```vue
<script setup lang="ts">
import { useMotionBlockUI } from 'nuxt-motion-block/runtime/composables/useMotionBlockUI'
import spotlight from 'nuxt-motion-block/runtime/theme/spotlight'

const props = withDefaults(defineProps<{
  class?: string
  ui?: Record<string, any>
}>(), {
  class: undefined,
  ui: undefined,
})

const { props: uiProps, ui } = useMotionBlockUI({
  name: 'spotlight',
  props,
  theme: spotlight,
})
</script>

<template>
  <div :class="ui.root({ class: [uiProps.ui?.root, uiProps.class] })" />
</template>
```

Components registered through Nuxt Motion Block expose their themes from `nuxt-motion-block/runtime/theme`, so you can also import a theme and extend it directly.

## Theme colours in components and canvas

Nuxt UI exposes its semantic colours as CSS variables — `--ui-primary`, `--ui-text-muted`, `--ui-border`, and so on — which is exactly what you want in a stylesheet. A `<canvas>` fill, a 2D stroke or a WebGL uniform cannot take a `var()` reference, though; it needs a concrete colour.

Nuxt Motion Block closes that gap for you. Every component that paints to canvas or WebGL accepts a Nuxt UI token (or any CSS colour) and converts it to the right format on its own:

```vue
<template>
  <!-- DottedGlowBackground: pass any --ui-* or --color-* variable -->
  <MDottedGlowBackground
    color-light-var="--ui-text-dimmed"
    glow-color-light-var="--ui-primary"
    glow-color-dark-var="--ui-primary"
  />

  <!-- AiOrb: colours accept any CSS colour, including var(--ui-*) -->
  <MAiOrb :colors="['var(--ui-primary)', 'var(--ui-secondary)']" />

  <!-- DitherChart: omit `palette` and it follows --ui-primary -->
  <MDitherChart :data="data" />

  <!-- Cursors: options.color is a theme colour name -->
  <canvas ref="canvasRef" />
</template>

<script setup lang="ts">
import { ref } from 'vue'

const canvasRef = ref()
useCanvasCursor(canvasRef, { color: 'primary' })
</script>
```

A token always resolves to the colour your theme is actually rendering — light or dark — and re-resolves when the colour mode changes. You can equally set a colour directly: every one of these props also accepts a plain CSS colour string, so a token is a convenience, not a requirement.

When you need the same behaviour in your own code, the [`useThemeColor`](https://nuxt-motion-block.pages.dev/composables/use-theme-color) composable hands you the resolved colour reactively:

```vue
<script setup lang="ts">
const primary = useThemeColor('primary') // Ref<'rgb(0, 220, 130)' | null>
</script>
```


## Sitemap

See the full [sitemap](https://nuxt-motion-block.pages.dev/sitemap.md) for all pages.
