All docs
Concepts · Theme

Theme

Themes are how you re-skin Caret without rewriting the components. A theme is a deeply-nested object whose leaves are token values — colors, motion durations, symbols, spacing, typography. Three application surfaces: globally, per React subtree, or per call.

The default theme

defaultTheme lands in your project after npx caret add theme — the file at caret/theme/default.ts exports it. You almost never read it directly: call useTheme() inside an Ink component or rely on Caret's components doing the lookup for you.

ts
import { defaultTheme } from '../caret/theme/default.js'

console.log(defaultTheme.colors.accent.default)  // '#5882f7'
console.log(defaultTheme.motion.duration.default) // 200

Globally re-skin with setTheme

Call once at startup before any Caret component renders. Subsequent calls take effect for the next render but don't roll back what's already painted.

src/index.ts
import { caret } from './caret'

caret.theme.set({
  colors: {
    accent: { default: '#FF6B35' },
  },
  symbols: {
    anchor: '◆',
  },
})

Overrides are merged shallowly per top-level key. You only specify leaves you want to change — Caret fills the rest from the default.

Subtree overrides with ThemeProvider

For React-tree scoped overrides — useful when one section of a CLI needs a different palette without affecting the rest.

tsx
import { ThemeProvider } from '../caret/theme/context.js'

function DangerZone() {
  return (
    <ThemeProvider theme={{ colors: { accent: { default: '#e5482d' } } }}>
      <DestructiveActions />
    </ThemeProvider>
  )
}

Reading the active theme with useTheme

Inside an Ink component, useTheme() returns the merged theme that's active at this point in the tree.

tsx
import { useTheme, Box, Text } from 'ink'

function Heading({ text }: { text: string }) {
  const theme = useTheme()
  return (
    <Box>
      <Text color={theme.colors.accent.default}>{theme.symbols.anchor} </Text>
      <Text bold>{text}</Text>
    </Box>
  )
}

Per-call overrides

Every Caret component accepts an optional theme option. It's merged with the active theme just for that single call — no global side-effects.

ts
spinner('Deploying', deploy, {
  theme: {
    colors: { accent: { default: '#10B981' } },
  },
})

Precedence

From lowest to highest priority — later wins:

SourcePriority
defaultThemeLowest — applied if nothing else overrides
caret.theme.set(...)Global override, replaces defaultTheme leaves
<ThemeProvider theme={...}>React subtree override
component({ theme: ... })Highest — wins for that single call
Brand accent only
Caret's manifesto says the brand accent is a fixed truecolor. You can override it, but the recommendation is: pick one once and stick to it across your CLI's lifetime. That accent is what makes a Caret CLI recognizable across any terminal.

Token reference is at Tokens; capability detection that gates which tokens are actually rendered is at Capability detection.