Skip to main content
The merge function and its merge.all variant are utilities for deeply merging Theme UI theme objects. They are used internally by ThemeProvider and can be used to combine themes manually.

Import

import { merge } from 'theme-ui'

Signature

merge

function merge(a: Theme, b: Theme): Theme
a
Theme
required
The base theme object
b
Theme
required
The theme object to merge into the base
result
Theme
A new theme object with deeply merged properties from both themes

merge.all

function merge.all<A, B>(a: A, b: B): A & B
function merge.all<A, B, C>(a: A, b: B, c: C): A & B & C
function merge.all<A, B, C, D>(a: A, b: B, c: C, d: D): A & B & C & D
function merge.all<T = Theme>(...args: Partial<T>[]): T
...args
Partial<Theme>[]
required
Any number of theme objects to merge together
result
Theme
A new theme object with deeply merged properties from all themes

Usage

Basic Merge

Merge two theme objects together:
import { merge } from 'theme-ui'

const baseTheme = {
  colors: {
    primary: 'blue',
    background: 'white',
  },
  fonts: {
    body: 'system-ui, sans-serif',
  },
}

const darkTheme = {
  colors: {
    background: 'black',
    text: 'white',
  },
}

const merged = merge(baseTheme, darkTheme)
// Result:
// {
//   colors: {
//     primary: 'blue',
//     background: 'black',  // overridden
//     text: 'white',        // added
//   },
//   fonts: {
//     body: 'system-ui, sans-serif',
//   },
// }

Merge Multiple Themes

Use merge.all to combine three or more themes:
import { merge } from 'theme-ui'

const base = {
  colors: { primary: 'blue' },
}

const dark = {
  colors: { background: 'black' },
}

const custom = {
  colors: { accent: 'purple' },
}

const result = merge.all(base, dark, custom)
// Result:
// {
//   colors: {
//     primary: 'blue',
//     background: 'black',
//     accent: 'purple',
//   },
// }

Extending a Theme

import { merge } from 'theme-ui'
import { base } from '@theme-ui/presets'

const myTheme = merge(base, {
  colors: {
    primary: 'tomato',
    secondary: 'cyan',
  },
  buttons: {
    custom: {
      bg: 'primary',
      color: 'white',
    },
  },
})

Merge Behavior

Deep Merging

The function deeply merges nested objects:
const result = merge(
  {
    colors: {
      primary: 'blue',
      modes: {
        dark: { primary: 'lightblue' },
      },
    },
  },
  {
    colors: {
      secondary: 'green',
      modes: {
        dark: { secondary: 'lightgreen' },
      },
    },
  }
)
// Result:
// {
//   colors: {
//     primary: 'blue',
//     secondary: 'green',
//     modes: {
//       dark: {
//         primary: 'lightblue',
//         secondary: 'lightgreen',
//       },
//     },
//   },
// }

Array Handling

Arrays in the second theme replace arrays in the first theme (not concatenated):
const result = merge(
  { fontSizes: [12, 14, 16, 20, 24] },
  { fontSizes: [16, 18, 20] }
)
// Result: { fontSizes: [16, 18, 20] }
This also works when overriding arrays with primitives:
const result = merge(
  { fontSizes: [12, 14, 16] },
  { fontSizes: 16 as any }
)
// Result: { fontSizes: 16 }

React Components

React components and elements are not merged, but replaced:
const Button = (props) => <button {...props} />
const CustomButton = forwardRef((props, ref) => (
  <button ref={ref} {...props} />
))

const result = merge(
  { components: { Button } },
  { components: { Button: CustomButton } }
)
// Result: { components: { Button: CustomButton } }

Internal Implementation

From packages/core/src/index.ts:96-122:
const deepmergeOptions: deepmerge.Options = {
  isMergeableObject: (n) => {
    return (
      !!n &&
      typeof n === 'object' &&
      (n as React.ExoticComponent).$$typeof !== REACT_ELEMENT &&
      (n as React.ExoticComponent).$$typeof !== FORWARD_REF
    )
  },
  arrayMerge: (_leftArray, rightArray) => rightArray,
}

export const merge = (a: Theme, b: Theme): Theme =>
  deepmerge(a, b, deepmergeOptions)

function mergeAll<A, B>(a: A, B: B): A & B
function mergeAll<A, B, C>(a: A, B: B, c: C): A & B & C
function mergeAll<A, B, C, D>(a: A, B: B, c: C, d: D): A & B & C & D
function mergeAll<T = Theme>(...args: Partial<T>[]) {
  return deepmerge.all<T>(args, deepmergeOptions)
}

merge.all = mergeAll

Examples from Tests

Deep Object Merge

const result = merge(
  {
    beep: 'boop',
    hello: {
      hi: 'howdy',
    },
  },
  {
    bleep: 'bloop',
    hello: {
      ohaiyo: 'osu',
    },
  }
)
// Result:
// {
//   beep: 'boop',
//   bleep: 'bloop',
//   hello: {
//     hi: 'howdy',
//     ohaiyo: 'osu',
//   },
// }

Merge All

const result = merge.all(
  { beep: 'boop' },
  { bleep: 'bloop' },
  { plip: 'plop' }
)
// Result:
// {
//   beep: 'boop',
//   bleep: 'bloop',
//   plip: 'plop',
// }

Use Cases

Theme Variants

Create theme variants by merging a base with modifications:
import { merge } from 'theme-ui'

const baseTheme = { /* ... */ }

export const lightTheme = merge(baseTheme, {
  colors: { background: 'white', text: 'black' },
})

export const darkTheme = merge(baseTheme, {
  colors: { background: 'black', text: 'white' },
})

Composing Preset Themes

import { merge } from 'theme-ui'
import { base } from '@theme-ui/presets'
import { typographyTheme } from './typography'
import { colorTheme } from './colors'

export const theme = merge.all(
  base,
  typographyTheme,
  colorTheme,
  {
    // Your custom overrides
  }
)

Component Library Theming

import { merge } from 'theme-ui'
import { systemTheme } from './system'

export function createTheme(userTheme: Theme) {
  return merge(systemTheme, userTheme)
}

Build docs developers (and LLMs) love