Skip to main content

Migrating to Emotion-based Abyss Theming

This guide will help you migrate your application from the previous Stitches-based theming system to the new Emotion-based implementation in Abyss.

Good news! The migration effort should be minimal for most applications. We've designed the new Emotion-based implementation to be as compatible as possible with existing code. In many cases, your application will continue to work with just a few adjustments (see Breaking Changes below) after installing the new version but with the following added benefits:

Overview of changes

  • server-side rendering support
  • Improved style isolation for parcels with Shadow DOM support
  • More consistent styling behavior across different environments and host applications
  • Granular control of how styles are being processed and injected into the DOM

Breaking changes

ThemeProvider requirements

Before: CSS variables were added to the :root element whether or not createTheme or ThemeProvider was used and therefore some styles would still be applied to Abyss components.

After: In the new Emotion-based implementation ThemeProvider + createTheme is required:

  • No styles will be applied to Abyss components if they're not encapsulated within a ThemeProvider that includes a theme provided by createTheme.
  • CSS variables are only scoped to the ThemeProvider wrapper elements
  • No default theme is created if a ThemeProvider is used without a theme

Provider props

The following props have been moved from ThemeProvider to createTheme:

  • brandAssetsCdn
  • includeBaseCss

Before:

<ThemeProvider
theme={theme}
brandAssetsCdn="https://example.com/assets"
includeBaseCss={true}
>
<App />
</ThemeProvider>

After:

const theme = createTheme('uhc', {
brandAssetsCdn: 'https://example.com/assets',
includeBaseCss: false,
});
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>;

globalCss

The globalCss utility from @uhg-abyss/web/tools/styled was part of the v1 Stitches API and has been deprecated. Please use Emotion's Global component from @uhg-abyss/web/ui/ThemeProvider to define global styles instead.

Before:

import { ThemeProvider } from '@uhg-abyss/web/ui/ThemeProvider';
import { createTheme } from '@uhg-abyss/web/tools/theme';
import { globalCss } from '@uhg-abyss/web/tools/styled';
const globalStyles = globalCss({
body: {
backgroundColor: '#f0f0f0',
},
});
const theme = createTheme('uhc');
export function App() => {
globalStyles();
return <ThemeProvider theme={theme}>Your app</ThemeProvider>
}

After:

import { ThemeProvider, Global } from '@uhg-abyss/web/ui/ThemeProvider';
import { createTheme } from '@uhg-abyss/web/tools/theme';
const globalStyles = {
body: {
backgroundColor: '#f0f0f0',
},
};
const theme = createTheme('uhc');
const App = () => {
return (
<ThemeProvider theme={theme}>
<Global styles={globalStyles} />
...
</ThemeProvider>
);
};

Styling API changes

While the underlying styling engine has changed from Stitches to Emotion, we've worked hard to preserve the styled utility API to ensure minimal migration work. Most of your existing styling configurations should continue to work without changes.

However, due to fundamental differences between the styling engines, some specific patterns may require updates. The following are the most common patterns we've identified, but given the potential variations and complexity of custom styling, this list isn't exhaustive:

Component selectors

Replace component reference selectors with class-based selectors:

- [`${StyledTrigger}[data-state=open] &`]: { ... }
+ '.abyss-accordion-trigger[data-state=open] &': { ... }

CSS pseudo-selectors

Some pseudo-selectors need updates for compatibility:

- '&:first-child': { ... }
+ '&:first-of-type': { ... }

Adjacent sibling selectors

Keep using class-based selectors for adjacent siblings:

- '& + &': { ... }
+ '& + .abyss-form-input-wrapper': { ... }

Content property

String values in the content property need to be properly escaped:

- content: '',
+ content: "''",

CSS property names

Use camelCase for CSS property names instead of kebab-case with quotes:

- 'align-items': 'flex-start',
+ alignItems: 'flex-start',

Migration scenarios

Basic usage

For most applications, simply update your ThemeProvider usage and ensure all components that need theme access are within a ThemeProvider:

import { ThemeProvider } from '@uhg-abyss/web/ui/ThemeProvider';
import { createTheme } from '@uhg-abyss/web/tools/theme';
const theme = createTheme('uhc');
export default function App() {
return (
<ThemeProvider theme={theme}>
<YourApp />
</ThemeProvider>
);
}

Next.js server-side rendering

The Emotion-based ThemeProvider has built-in support for Next.js server-side rendering (SSR). It integrates with Next.js's style extraction mechanisms to prevent style flashing during hydration and ensure consistent styling between server and client. For most Next.js applications, using ThemeProvider alone is sufficient:

// Basic Next.js SSR setup
import { ThemeProvider } from '@uhg-abyss/web/ui/ThemeProvider';
import { createTheme } from '@uhg-abyss/web/tools/theme';
const theme = createTheme('uhc');
export default function App({ children }) {
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
}

For more control over style extraction and injection, use the NextStyleProvider (works with both App Router and Pages Router):

import { NextStyleProvider } from '@uhg-abyss/web/ui/ThemeProvider/NextStyleProvider';
import { ThemeProvider } from '@uhg-abyss/web/ui/ThemeProvider';
import { createTheme } from '@uhg-abyss/web/tools/theme';
const theme = createTheme('uhc');
// For App Router: app/layout.js
// For Pages Router: pages/_app.js
export default function App({ children, Component, pageProps }) {
const content = Component ? <Component {...pageProps} /> : children;
return (
<NextStyleProvider cacheOptions={{ key: 'my-app' }}>
<ThemeProvider theme={theme}>{content}</ThemeProvider>
</NextStyleProvider>
);
}

Style isolation and Parcels

Another benefit of the new Emotion-based theming system is improved style isolation for parcels. The StyleRootProvider with Shadow DOM support ensures that styles from the host application don't leak into your parcel and vice versa.

// MyParcel.jsx
import React from 'react';
import { StyleRootProvider } from '@uhg-abyss/web/ui/ThemeProvider/StyleRootProvider';
import { ThemeProvider } from '@uhg-abyss/web/ui/ThemeProvider';
import { createTheme } from '@uhg-abyss/web/tools/theme';
const theme = createTheme('uhc');
export const MyParcel = () => (
<StyleRootProvider
useShadowDom
theme={theme}
cacheOptions={{ key: 'my-parcel' }}
>
<ThemeProvider theme={theme}>
{/* Your parcel content here */}
</ThemeProvider>
</StyleRootProvider>
);

Key Benefits:

  • Complete style isolation from host applications
  • Consistent theming regardless of embedding context
  • No class name collisions with host applications
  • Styles scoped only to your parcel for better performance

Advanced usage

For advanced use cases, refer to the documentation for:

  • StyleRootProvider - For applications that need fine-grained control over CSS injection or Shadow DOM isolation
  • NextStyleProvider - Optimized for Next.js applications with server-side rendering support
Table of Contents