Voice UI Kit

Styling and Theming

Using, customizing, and extending the Voice UI Kit styles

The Voice UI Kit ships with a minimal default theme that’s meant to be customized and extended. It’s not a “creative design library” with strong opinions; it’s a flexible foundation for building your own voice-first UIs.

Under the hood it uses Tailwind CSS v4 and is designed to play nicely with your existing styles or Tailwind setup.

  • @pipecat-ai/voice-ui-kit/styles - The default theme
  • @pipecat-ai/voice-ui-kit/styles.scoped - The default theme, scoped to a .vkui-root class
  • Dark mode support
  • ShadUI compatibility (components registry coming soon!)
  • Optional utilities: @pipecat-ai/voice-ui-kit/utilities for helpful extras (see below)

Setup

The Voice UI Kit CSS can be imported from the @pipecat-ai/voice-ui-kit/styles package.

global.css
@import "@pipecat-ai/voice-ui-kit/styles";

:root {
  /* etc */
}

If you're using Tailwind in your project, you can import it alongside your own custom styles:

global.css
@import "tailwindcss";
@import "tw-animate-css";

@import "@pipecat-ai/voice-ui-kit/styles";

:root {
  /* etc */
}

Alternatively, you can include the styles in your application code:

layout.tsx
import "@pipecat-ai/voice-ui-kit/styles";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

Customization

Developers can approach customization in a few different ways:

  • Passing custom styles or class names through to the components.
  • Overriding CSS variables, such as colors, fonts, etc.
  • Installing individual components from the registry and extending them (ejecting.)

Theme Provider and useTheme

Wrap your app with ThemeProvider to enable light/dark (or custom) themes and persistence.

layout.tsx
import { ThemeProvider } from "@pipecat-ai/voice-ui-kit";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ThemeProvider defaultTheme="system" storageKey="voice-ui-kit-theme">
      {children}
    </ThemeProvider>
  );
}

Props

PropTypeDefault
children
React.ReactNode
-
defaultTheme?
"system" | string
"system"
storageKey?
string
"voice-ui-kit-theme"
disableStorage?
boolean
false

useTheme hook

Use useTheme anywhere under the provider to read and set the theme.

import { useTheme } from "@pipecat-ai/voice-ui-kit";

export function ThemeToggle() {
  const { theme, resolvedTheme, setTheme } = useTheme();
  return (
    <div>
      <div>theme: {theme}</div>
      <div>resolved: {resolvedTheme}</div>
      <button onClick={() => setTheme("light")}>Light</button>
      <button onClick={() => setTheme("dark")}>Dark</button>
      <button onClick={() => setTheme("system")}>System</button>
    </div>
  );
}

Create a custom theme

my-theme.css
@custom-variant mytheme (&:is(.mytheme *));

@theme {
  /* Optional: local keyframes, shadows, etc. */
}

.mytheme {
  --font-sans: system-ui, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  --color-background: #000;
  --color-foreground: #e5e7eb;
  --color-primary: #22c55e;
  --color-secondary: #14532d;
  --color-border: #22c55e;
  --color-input: #0f172a;
  --color-accent: color-mix(in srgb, var(--color-primary) 10%, transparent);
  --color-accent-foreground: var(--color-foreground);
}

Apply it by toggling a class on a root container (or html if you prefer global):

import "@pipecat-ai/voice-ui-kit/styles";
import "./my-theme.css";

export function App() {
  return (
    <div className="mytheme">
      {/* app */}
    </div>
  );
}

Switching themes at runtime

You can switch themes programmatically either globally (ThemeProvider on html) or scoped to a container.

import { ThemeProvider, useTheme } from "@pipecat-ai/voice-ui-kit";

export default function App() {
  return (
    <ThemeProvider defaultTheme="system">
      <Toolbar />
      <YourApp />
    </ThemeProvider>
  );
}

function Toolbar() {
  const { theme, resolvedTheme, setTheme } = useTheme();
  return (
    <div>
      <span>current: {resolvedTheme}</span>
      <button onClick={() => setTheme("light")}>Light</button>
      <button onClick={() => setTheme("dark")}>Dark</button>
      <button onClick={() => setTheme("system")}>System</button>
    </div>
  );
}
import "@pipecat-ai/voice-ui-kit/styles.scoped";
import { ThemeProvider, useTheme } from "@pipecat-ai/voice-ui-kit";

export default function App() {
  return (
    <ThemeProvider defaultTheme="dark">
      <ScopedRoot />
    </ThemeProvider>
  );
}

function ScopedRoot() {
  const { resolvedTheme } = useTheme();
  // IMPORTANT: when using scoped styles, put the theme class on `.vkui-root`
  return (
    <div className={`vkui-root ${resolvedTheme}`}>
      <YourApp />
    </div>
  );
}

If you're using the scoped import (@pipecat-ai/voice-ui-kit/styles.scoped), set the theme class on the .vkui-root element instead of html.

Themes

The default theme is intentionally light-touch. If you want a quicker start, we also provide optional themes you can use as-is or adapt.

global.css
@import "@pipecat-ai/voice-ui-kit/styles"; 
@import "@pipecat-ai/voice-ui-kit/themes/terminal";
Note: You must import the Voice UI Kit styles before importing a theme.

Theme files are primarily CSS variable overrides.

Utilities CSS

You can optionally import the utilities bundle for extra helpers used by the UI Kit.

global.css
@import "tailwindcss";
@import "@pipecat-ai/voice-ui-kit/styles";
@import "@pipecat-ai/voice-ui-kit/utilities";

Why import utilities?

  • Provides additional design tokens and helpers (e.g., scanlines, grid/stripe backgrounds, sizing shortcuts) used by components and examples
  • Ensures custom compositions with UI Kit components merge correctly without conflicts
  • Reduces duplication of one-off utility CSS in your app

Scoping

The Voice UI Kit components are intended to be placed in a developer's own application, inheriting from their own styles or Tailwind theme.

Sometimes, however, it's preferable to isolate styles from your own. The Voice UI Kit bundle includes a fully scoped CSS file that can be used instead:

global.css
@import "@pipecat-ai/voice-ui-kit/styles.scoped";

/* Your application CSS file */

The Voice UI Kit theme will now only apply when wrapped in a .vkui-root class, and declarations from your own Tailwind theme will not be applied.

<div class="vkui-root">
  <span class="vkui:flex">✅ Scoped correctly</span>
</div>

<div>
  <span class="vkui:flex">❌ Not scoped</span>
</div>

Dark mode

The Voice UI Kit theme supports dark mode using a :root.dark selector:

<html class="dark">
  <body>
    ...
  </body>
</html>

Class merging

The Voice UI Kit uses custom design tokens and utilities that aren't part of standard Tailwind CSS:

  • Custom shadow classes: shadow-xshort, shadow-short, shadow-long, shadow-xlong
  • Custom button sizing: button-sm, button-md, button-lg, button-xl and their icon variants
  • Custom spacing tokens: element-xs, element-sm, element-md, element-lg, element-xl, element-2xl

These utilities support consistency across components and primitives, as well as reduce the amount of inline classes required.

Without proper merge handling, these custom classes would conflict with each other or not merge correctly when combined with standard Tailwind classes.

Usage

The cn utility function is used throughout the Voice UI Kit components to merge class names intelligently:

import { cn } from "@pipecat-ai/voice-ui-kit";

// This will properly merge shadow classes
<Card className={cn("shadow-short", className)} />

// This will properly merge button sizing
<Button className={cn("button-lg", customClasses)} />

This ensures that when developers pass custom classes to components, they merge correctly with the Voice UI Kit's design system without conflicts.

Variable list

See available variables in the source index.css.