Skip to main content

createTheme

Tool to create and modify themes.

github
View source code
import { createTheme } from '@uhg-abyss/web/tools/theme';

The createTheme tool uses Abyss's preset themes and allows you to override those themes to fit your design needs. createTheme is used in conjunction with ThemeProvider and leverages Emotion for styling.

Usage

createTheme accepts two arguments. The first is the name of a default theme and is required. There are currently two themes available: 'uhc' and 'optum'. The second argument is an optional themeConfig object that can include theme overrides and other configuration options.

createTheme(
themeName: 'uhc' | 'optum',
themeConfig?: {
/** Theme token overrides */
theme?: DeepPartial<ThemeTokens>;
/** Global CSS style overrides */
css?: Record<string, React.CSSProperties>;
/** Whether to include base CSS styles (reset, normalize, foundational styles). @deprecated Use `selfContainedBaseCss` instead. @default true */
includeBaseCss?: boolean;
/** Whether to scope base CSS under the theme class instead of globally. @deprecated Use `selfContainedBaseCss` instead. @default false */
scopeBaseCss?: boolean;
/** Suppress global base CSS and have each component carry its own base resets, so Abyss styles don't bleed into a host app. @default false */
selfContainedBaseCss?: boolean;
/** Whether to include theme-specific font face definitions. @default true */
includeFonts?: boolean;
/** Whether to include default heading styles (h1-h6). @default true */
includeHeadings?: boolean;
/** Custom CDN URL for brand assets (logos, icons, brandmarks) */
brandAssetsCdn?: string;
/** Use Enterprise Sans font family (UHC theme only). @default false */
enterpriseFont?: boolean;
/** Override the theme name */
themeName?: string;
/** Enable CSS variable caching. @default true */
enableCSSVariableCache?: boolean;
/** Enable theme object caching. @default false */
enableThemeCache?: boolean;
}
): BaseTheme;

Theme overrides

The themeConfig object accepts theme and css properties to override the default theme tokens and/or create custom tokens that can be used in the styled tool or the available css prop on each component. This allows teams to customize the theme for specific projects or brands in a single location.

UHC theme example

import { ThemeProvider } from '@uhg-abyss/web/ui/ThemeProvider';
import { createTheme } from '@uhg-abyss/web/tools/theme';
const themeConfig = {
theme: {
breakpoints: {
xs: 0,
sm: 360,
md: 744,
lg: 1248,
},
colors: {
'core.color.brand.5': '#EDF3FB',
'core.color.brand.10': '#E3EEFA',
'core.color.brand.20': '#D9E9FA',
},
sizes: {...},
space: {...},
fontSizes: {...},
fonts: {...},
fontWeights: {...},
lineHeights: {...},
letterSpacings: {...},
borderWidths: {...},
borderStyles: {...},
radii: {...},
shadows: {...},
opacities: {...},
},
css: { // provide custom global css overrides
p: {
marginBottom: '10px',
},
},
};
const theme = createTheme('uhc', themeConfig);
const App = () => {
return <ThemeProvider theme={theme}>...</ThemeProvider>;
};
ReactDOM.render(<App />, document.getElementById('root'));

Optum theme example

import { ThemeProvider } from '@uhg-abyss/web/ui/ThemeProvider';
import { createTheme } from '@uhg-abyss/web/tools/theme';
const themeConfig = {
theme: {
colors: {
'core.color.brand.70': '#0C55B8',
'core.color.brand.80': '#004BA0',
'core.color.brand.100': '#002677',
},
},
};
const theme = createTheme('optum', themeConfig);
const App = () => {
return <ThemeProvider theme={theme}>...</ThemeProvider>;
};

Abyss theme tokens

You can create your own themes by white labeling and applying overrides to our theme tokens. Please see the white labeling guides for more information.

For a quick start, you can also use our Live Token Editor, found in the header of this documentation site, to create and export your own themes. These can then be imported into your project and passed into createTheme as the theme override.

Live Token Editor interface

Extending themes

If you need to create variations of an existing theme or apply overrides in nested component contexts, use extendTheme instead. extendTheme is optimized for creating theme variations and nested theme contexts with minimal CSS overhead.

Font configuration

The default font for the UHC theme is UHC Sans. Teams looking to utilize Enterprise Sans can do so by setting the enterpriseFont property to true in the themeConfig object as shown below. This flag only applies to the UHC theme.

const themeConfig = {
enterpriseFont: true,
};
const theme = createTheme('uhc', themeConfig);

For more information on fonts and other brand related information, please see the following Brand documentation.

Self-contained component styles

Opt-in preview

selfContainedBaseCss is fully opt-in and defaults to false. Existing consumers are completely unaffected until they add the flag. When enabled, Abyss components render visually identically to the default configuration. The only difference is where the base resets come from (each component instead of a shared global sheet).

Abyss is moving away from injecting global base CSS (reset, normalize, foundational styles) altogether. Global styles have a few downsides:

  • Host app bleed: a global stylesheet affects the host app's own elements too, not just Abyss's.
  • Load-order/specificity fragility: whether a rule applies depends on where it lands relative to everything else on the page.
  • Harder to reason about locally: explaining a component's look requires knowing about a separate, page-wide stylesheet.

In V3, every Abyss component will always carry its own base resets internally, with no global stylesheet at all. selfContainedBaseCss: true lets you opt into that model today:

import { ThemeProvider } from '@uhg-abyss/web/ui/ThemeProvider';
import { createTheme } from '@uhg-abyss/web/tools/theme';
const theme = createTheme('optum', {
selfContainedBaseCss: true,
});
const MicroFrontend = () => {
return <ThemeProvider theme={theme}>...</ThemeProvider>;
};

What the base styles cover

Rather than injecting a single global stylesheet, each Abyss component internally carries only the base resets it actually needs. A Button carries the native button reset, a Link carries the anchor reset, a layout component carries box-sizing: border-box, and so on. With the flag enabled, the combined visual output of Abyss components is identical to the current default configuration.

The full set of rules that move from the global sheet into per-component styles is defined across three source files: cssReset.js, cssNormalize.js, and cssElements.js. Scanning those is the fastest way to know exactly what your raw HTML elements previously inherited for free.

Disclaimer

Currently, Abyss's base CSS is injected at runtime, so it lands after most stylesheets and wins ties by load order. With selfContainedBaseCss: true, that global sheet is no longer injected. Rules from a custom stylesheet or one from a different framework that Abyss's stylesheet had been suppressing will start applying.

If you see unexpected styling on non-Abyss elements after enabling the flag, scope or raise the specificity of your own CSS to address it. Restoring Abyss's global reset would reintroduce the bleed you opted out of.

Migrating raw HTML and custom components

Because the global sheet is no longer injected, raw HTML elements (<img>, <p>, <ul>, <h2>, etc.) and your own styled components sitting alongside Abyss components won't automatically receive the base resets. You may have some migration work if your app relies on those rules being present globally.

The fastest path is to restore them all at once with the Global component, which follows the same rules, it's just your call instead of Abyss's:

import { Global } from '@uhg-abyss/web/ui/ThemeProvider';
// Add this once near the root of your micro-frontend
<Global
styles={{
'*, *:before, *:after': { boxSizing: 'border-box' },
'ol, ul': { listStyle: 'none' },
p: { marginBottom: '10px' },
img: { maxWidth: '100%', height: 'auto' },
}}
/>;

If you prefer more granular control or want to avoid any global surface area, here are the rules with the most visible impact to handle element-by-element:

<img> — loses max-width: 100%, can overflow its container

// Before
<img src="" alt="" />
// After
<img src="" alt="" style={{ maxWidth: '100%', height: 'auto', display: 'block' }} />

<p> — loses margin-bottom: 10px

// Before
<p>Some text</p>
// After — inline
<p style={{ marginBottom: '10px' }}>Some text</p>
// After — use Abyss (carries the reset itself)
<Text asElement="p">Some text</Text>

<h1>-<h6> — browser-default margin reappears (~0.67-0.83em top + bottom)

// Before
<h2>Title</h2>
// After — inline
<h2 style={{ margin: 0 }}>Title</h2>
// After — use Abyss (carries the reset itself)
<Heading level={2}>Title</Heading>

<ul> / <ol> — list bullets and 40px indent return

// Before
<ul></ul>
// After
<ul style={{ listStyle: 'none', margin: 0, padding: 0 }}></ul>

<a> — loses brand link color and text-decoration: none

// Before
<a href="/path">Link text</a>
// After — use Abyss (carries its own anchor reset)
<Link href="/path">Link text</Link>

<button> — loses appearance: none; border: none; cursor: pointer

// Before
<button onClick={handleClick}>Click me</button>
// After — use Abyss
<Button onClick={handleClick}>Click me</Button>
// After — inline, if a raw button is needed
<button onClick={handleClick} style={{ appearance: 'none', border: 'none', cursor: 'pointer', background: 'none' }}>
Click me
</button>

Scoped base styles for micro-frontends (deprecated)

An alternative to selfContainedBaseCss, scopeBaseCss: true nests the reset/normalize/foundational styles under the theme's root class (:where(.abyss-XXXXX)) instead of suppressing them. This avoids specificity conflicts, but still ships a global stylesheet.

const theme = createTheme('optum', {
scopeBaseCss: true,
});

Comparing base CSS options

All three will be removed in V3

includeBaseCss and scopeBaseCss are now deprecated so teams can opt into selfContainedBaseCss ahead of time instead of migrating all at once. In V3, there will be no flag at all; self-contained component styles will be the only behavior.

OptionDefaultWhere the base CSS ends up
includeBaseCsstrueInjected globally, unscoped
scopeBaseCssfalseInjected globally, scoped under :where(.abyss-XXXXX)
selfContainedBaseCssfalseNot injected globally, carried internally by each component
Table of Contents