Components

Theme

Seeds provides a default theme that offers easy access to a wide array of design tokens.

The theme file provides structured access to design tokens in a format that is consistent and easily consumable. You can use theme values with system props, or access the theme object directly from within your own Styled Components.

Already know how to use the theme?Jump to the reference

Tokens and values

Tokens and values come in a few different forms: hard coded values, literal tokens, and semantic tokens. Only some of which are available from the Seeds theme.

Hard coded value

Hard coded values are values like pixel, hex, or even string values. The Seeds theme does not provide any hard coded values.

hex-values: #FFFFFF;
pixel-values: 12px;

One way to identify a hard coded value is to ask yourself whether or not it will update dynamically if something in the theme changes. If the answer is no, then it's probably hard coded.

something-like: "transparent";

It's important to remember that when using tokens in something like CSS shorthand, some values may be hard coded while others aren't. This is ok as long as the values that need to be dynamic are using semantic values.

border: 1px solid ${(p) => p.theme.colors.container.background.base};

Literal token

blue.100' or 'theme.colors.blue[100] = #dcf2ff

Literal tokens are design tokens that represent a particular value in our theme. Updating that value will change all instances of that token. However, literal tokens don't represent multiple values and therefore are not used for dynamic theming.

If the value behind a literal token is updated, that update will propagate to all instances of that token. However, a literal token can only represent a single value. Therefore, literal tokens will not update based on the theme. For that, you will need semantic tokens.

<Box bg="blue.100" width="100%" height="100px"></Box>

Toggle the above example between light and dark mode. Notice how the color of the box doesn't change. That is because the value of bg is using a literal token which only represents a single color value. While it doesn't change based on the theme, if we were to change the value of blue.100 from blue to white, blue.100 would now appear white.

This is important because we can define a core color palette by assigning hard coded values to literal tokens. We can then assign multiple literal tokens to a semantic token. If we want to change how blue.100 looks later, we change the hard coded value in a single place.

// A simplified example of a theme using
// hard coded values, literal tokens, and semantic tokens.
const coreColorPalette = {
blueLiteralToken: {
700: "#2079c3",
},
purpleLiteralToken: {
700: "#6f5ed3",
},
};
const themeA = {
semantic: {
token: coreColorPalette.blueLiteralToken[700],
},
};
const themeB = {
semantic: {
token: coreColorPalette.purpleLiteralToken[700],
},
};
// The color of box.background in App will depend on whether
// theme = themeA or themeB. Do you know what color the Box
// would be below?
function App({ theme = themeA }) {
return <Box bg={theme.semantic.token} height="100px"></Box>;
}

Semantic token

container.background.base = ?

Semantic tokens are essentially aliases of other tokens that are assigned to a specific purpose or meaning. For example, rather than asking what something looks like, we instead ask, "what is it?" or "what does it mean?". The answer to those questions will ultimately decide it's value.

Unlike literal tokens, semantic tokens can represent any number of values. By denoting an element a container and assigning the meaning of it's background to decorative, we can now access the color assigned to container.background.decorative in any theme.

<Box bg="container.background.decorative.blue" width="100%" height="100px"></Box>

Let's revisit our blue box from the literal token example. Notice how the color of the box changes when we toggle the theme to dark mode. We know that our is a container. We also know that it's background is blue purely for decorative reasons. By changing the background property (bg) to reflect it's purpose and meaning, it's now completely themed.

Examples

Let's take a look at a few more examples of semantic thinking.

<Box
bg="red.0"
borderRadius={600}
border="1px solid"
borderColor="red.800"
width="100%"
display="flex"
alignItems="center"
p={300}
>
<Icon mr={300} color="red.800" name="triangle-exclamation-outline" aria-label="Error" />
<Text.BodyCopy>
This is an error reminding us to use the banner component!
</Text.BodyCopy>
</Box>

If we look at the code in the above example, we can clearly see that we are dealing with a component whose purpose is to convey an error. Toggle the dark theme on to see what happens to the component.

While a component exists for this very scenario, we don't know the full context of why this one off was built. What we do know is that the copy contains the word "error" and the "triangle-exclamation-outline" icon is usually used for errors. With that information we can safely assume this is some kind of error messaging. Let's refactor it to use semantic values and take advantage of theming.

We know that is a container. It's purpose is to contain content. We also know that the background (bg) is red. We know based on the above context that red should likely mean error. So we give the bg property the token container.background.error.

container + background + error = container.background.error

We can apply that same line of thinking to the border by replacing background with border. Now we assign container.border.error to the borderColor property.

container + border + error = container.border.error

Finally, we can make sure the icon is colored appropriately by asking "what is it?" and "what does it mean?". It's an icon and it means error. So we set the color prop on to icon.error.

icon + error = icon.error

Now try toggling the theme to dark mode. It just works! By using semantic tokens we can ensure that our component styles will "just work" with not just dark mode, but any number of different themes.

<Box
bg="container.background.error"
borderRadius={600}
border="1px solid"
borderColor="container.border.error"
width="100%"
display="flex"
alignItems="center"
p={300}
>
<Icon mr={300} color="icon.error" name="triangle-exclamation-outline" aria-label="Error" />
<Text.BodyCopy>
This is an error reminding us to use the banner component!
</Text.BodyCopy>
</Box>

Reference

Accessing the theme

There are a few ways to use the theme in your work:

System props

System props reference the tokens from the theme to enable consumers to add custom styles to their components via a predetermined set of props.

You can reference which system props are available on a given component by checking the properties section of a component page.

<Box bg="container.background.error" />

Styled components

Styled Components can access the theme (on the component's prop object) directly via a key path.

margin: ${p => p.theme.space[500]};
background: ${p => p.theme.colors.container.background.base};

SCSS

Access the theme in SCSS files using our themed functions. Access a subset of the theme using the function of the same name (e.g., colors, shadows, radii), or use the generic function t to access any part of the theme.

.selector {
color: colors("text.body"); // alternatively, t("colors.text.body")
}

In order to use these functions, you must import them into your SCSS files directly or in a parent stylesheet with @import "themed". There is a themed file per theme, so you must point your CSS build tool to the correct theme folder (e.g., dist/themes/dark while generating dark mode CSS) in order to include the correct themed functions.

Color

Using colors

base represents the default color that an element should have without any modification. For example, a default blue link.

link.base

However, the base color of an element can be replaced by modifiers such as hover or error that allow you to change its appearance based on a state or purpose. Every element has a unique set of modifiers reflected in this reference.

link.hover

Certain elements like text don't have a base or modifiers and have to be carefully considered at each use.

text.headline

Additionally, more complex elements are composed of multiple colored parts.

form.background.base
form.border.base
form.placeholder.base

At some point, you'll need to color a component that doesn't match anything in this reference. In that case, try to color the component based on the elements it's built from.

<Message /> = container + text + buttons

Colors are broken down into categories. These categories represent common themeable elements that make up most interfaces.

App

background: (p) => p.theme.colors.app.background.base;
Base elementModifiers
app.background.base-

Container

Containers are any visible element whose specific purpose is to enclose content. Think , <div>

background: ${(p) => p.theme.colors.container.background};
Base elementModifiers
container.background.base
success
warning
error
info
opportunity
danger
decorative
selected
positive_sentiment
negative_sentiment
neutral_sentiment
container.border.base
success
warning
error
danger
info
opportunity
decorative
selected

Button

background: ${(p) => p.theme.colors.button.primary.background.base};
Primary

Used for the most important calls to action.

Base elementModifiers
button.primary.background.base
hover
active
button.primary.border.base-
button.primary.text.base
hover
Secondary

Used as supplementary calls to action.

Base elementModifiers
button.secondary.background.base
hover
active
button.secondary.border.base-
button.secondary.text.base
hover
Destructive

Used when the user's action has destructive consequences.

Base elementModifiers
button.destructive.background.base
hover
active
button.destructive.border.base-
button.destructive.text.base
hover
Pill

A low-level, subdued action with a big hover and click target.

Base elementModifiers
button.pill.background.base
hover
active
button.pill.border.base
hover
button.pill.text.base
hover
Placeholder

A subdued action that serves as a placeholder for adding content.

Base elementModifiers
button.placeholder.background.base
hover
active
button.placeholder.border.base-
button.placeholder.text.base
hover
Unstyled

An unstyled button used when total stylistic flexibilty is required.

Base elementModifiers
button.unstyled.background.base-
button.unstyled.border.base-
button.unstyled.text.base
hover
Base elementModifiers
link.base
hover

Text

Base elementModifiers
text.headline-
text.subtext-
text.body-
text.inverse-
text.error-
text.background.highlight-
text.background.selection#a1c2f8
text.decorative-

Icon

Base elementModifiers
icon.base
inverse
success
warning
error
danger
info
opportunity
applied
positive_sentiment
negative_sentiment
neutral_sentiment

Form

Base elementModifiers
form.background.base
selected
form.border.base
error
warning
selected
form.placeholder.base-

List item

Base elementModifiers
listItem.background.base
hover
selected

Overlay

Base elementModifiers
overlay.background.base-
overlay.text.base-
overlay.icon.base-

Elevation

boxShadow: (p) => p.theme.colors.elevation.base;
Base elementModifiers
elevation.base-

Network

Base elementModifiers
network.twitter-
network.twitter_like-
network.facebook-
network.facebook_audience_network-
network.linkedin-
network.instagram-
network.feedly-
network.analytics-
network.youtube-
network.messenger-
network.snapchat-
network.pinterest-
network.tumblr-
network.reddit-
network.tripadvisor-
network.glassdoor-
network.google_my_business-
network.google_business_messages-
network.google_play_store-
network.apple_app_store-
network.salesforce-
network.zendesk-
network.hubspot-
network.microsoft_dynamics-
network.yelp-
network.whatsapp-
network.tiktok-
network.threads-
network.trustpilot-
network.x-
network.x_like-

Dataviz

The dataviz palette is an ordered list of colors for use in data visualizations.

import { useContext } from 'react'
import { ThemeContext } from 'styled-components'
const generateChart = () => {
const theme = useContext(ThemeContext)
return (
theme.colors.dataviz.list.map((datavizColor, i) => {
<Box bg={datavizColor}>
Dataviz color {i}
</Box>
})
)
}
Base elementModifiers
dataviz.mapAn object containing an ordered list of dataviz colors
dataviz.listAn array of dataviz colors
dataviz.placeholder-

Space

Note: All space values are available as negative numbers as well. Simply prepend a hyphen before the value, i.e. <Box margin={-200} /> will give a -4px margin.

System propKey pathValue
0 theme.space[0]
100 / -100theme.space[100] / theme.space[-100]
200 / -200theme.space[200] / theme.space[-200]
300 / -300theme.space[300] / theme.space[-300]
350 / -350theme.space[350] / theme.space[-350]
400 / -400theme.space[400] / theme.space[-400]
450 / -450theme.space[450] / theme.space[-450]
500 / -500theme.space[500] / theme.space[-500]
600 / -600theme.space[600] / theme.space[-600]

Typography

Typography tokens encapsulate both font size and line height values.

System propKey pathValue
100theme.typography[100]
200theme.typography[200]
300theme.typography[300]
400theme.typography[400]
500theme.typography[500]
600theme.typography[600]
700theme.typography[700]
800theme.typography[800]
900theme.typography[900]
1000theme.typography[1000]
1100theme.typography[1100]
1200theme.typography[1200]

Font weight

System propKey pathValue
normaltheme.fontWeights.normal
semiboldtheme.fontWeights.semibold
boldtheme.fontWeights.bold
extraboldtheme.fontWeights.extrabold

Border width

System propKey pathValue
500theme.borderWidths[500]1px
600theme.borderWidths[600]2px
700theme.borderWidths[700]3px

Border radius

System propKey pathValue
innertheme.radii.inner500 token6px
outertheme.radii.outer600 token8px
pilltheme.radii.pill1000 token999999px

Box shadow

System propKey pathValue
lowtheme.shadows.low0px 2px 4px #2733333D
mediumtheme.shadows.medium0px 8px 16px #2733333D
hightheme.shadows.high0px 16px 32px #2733333D

Easing

System propKey pathValue
ease_intheme.easing[ease_in]cubic-bezier(.4, 0, .7, .2)
ease_outtheme.easing[ease_out]cubic-bezier(0, 0, .2, 1)
ease_inouttheme.easing[ease_inout]cubic-bezier(.4, 0, .2, 1)

Duration

System propKey pathValue
fasttheme.duration[fast].15s
mediumtheme.duration[medium].3s
slowtheme.duration[slow].6s

Breakpoints

Breakpoint values are not referenced directly in system props. Check out the system props page for more details on how to use responsive props.

Breakpoint values highly depend on the context in which the components are being used. These values are a good starting point, but they should likely be customized for most projects.
Index/nameKey pathValue
0 (Small)theme.breakpoints[0]900px
1 (Medium)theme.breakpoints[1]1200px
2 (Large)theme.breakpoints[2]1500px
3 (X Large)theme.breakpoints[3]1800px

Extending the theme

Approach

The base Racine theme should meet 90% of the use cases needed to build a UI but every product and brand is unique. There are cases where you may need to extend the theme. By doing so, you receive all the base theme has to offer and additionally can customize it. Extended themes should be accessible to everyone at Sprout Social.

Governance

To increase visibility, store extended theme files in Racine under the src/themes/extendedThemes directory. This ensures a singular place to collaborate on theme updates. When creating a theme file make sure to follow our specific naming convention.

How to create an extended theme

Create a new directory for your extended theme in Racine's extendedThemes directory. Within your extended theme, create directories for light and dark or any other modes your app will utilize. Finally, add a theme.js file to each mode's directory.

src/themes/extendedThemes/customTheme/light/theme.js
src/themes/extendedThemes/customTheme/dark/theme.js

To start our new theme, we'll first import the base Racine theme. We'll use this later for our new theme's base values.

import { theme as baseLightTheme } from '@sprout-social/racine'
// or
import { darkTheme as baseDarkTheme } from '@sprout-social/racine'

Next, we'll define a new object to hold our new theme's values. For this example, we'll call it customTheme.

// src/themes/extendedThemes/customTheme/light/theme.js
import { theme as baseLightTheme } from '@sprout-social/racine'
const customTheme = {};
export default customTheme

Spread the base theme into your custom theme. This copies all of the existing theme properties into your custom theme.

// src/themes/extendedThemes/customTheme/light/theme.js
import { theme as baseLightTheme } from '@sprout-social/racine'
const customTheme = {
...baseLightTheme,
};
export default customTheme

Now we can customize our extended theme. Let's add a new color property and override our primary button's background color.

// src/themes/extendedThemes/customTheme/light/theme.js
import { theme as baseLightTheme } from '@sprout-social/racine'
// You can add a new property to the theme object
// We recommend namespacing new properties with your zone or team name
const uniqueColorThing = {
text: {
base: baseLightTheme.colors.neutral[0],
hover: baseLightTheme.colors.neutral[0]
};
};
// Override values in the base theme
// Here's an example on how to override the primary button background color
const overridePrimaryButtonBackground = {
base: baseLightTheme.colors.purple[700],
hover: 'orange',
active: 'green'
};
const customTheme = {
// spread in the base theme
...baseLightTheme,
colors: {
// spread in the base theme's colors as to not override ALL colors.
...baseLightTheme.colors,
button: {
...baseLightTheme.colors.button,
primary: {
...baseLightTheme.colors.button.primary,
background: overridePrimaryButtonBackground
}
},
// spread in the new theme properties.
uniqueColorThing
}
};
export default customTheme;

Export your custom theme from the Racine index file.

export { customTheme } from "../themes/extendedThemes/customTheme/theme";

How to use an extended theme

Import the extended themes and ThemeProvider from Racine

import { customTheme, customDarkTheme, ThemeProvider } from '@sproutsocial/racine';

You can pass in your new theme into a ThemeProvider

render(
<ThemeProvider theme={isDarkMode ? customTheme : customDarkTheme}>
<Button>Default Theme</Button>
</ThemeProvider>
);

Working example

() => {
// racine/src/themes/extendedThemes/customTheme/light/theme.js
const overridePrimaryButtonBackground = {
base: theme.colors.purple[700],
hover: 'orange',
active: 'green'
}
// Create custom theme
const customTheme = {
...theme,
colors: {
...theme.colors,
button: {
...theme.colors.button,
primary: {
...theme.colors.button.primary,
background: overridePrimaryButtonBackground
}
}
}
}
// App.js
// import { Box, Button, customTheme, ThemeProvider } from '@sproutsocial/racine';
return (
<Box display="flex" justifyContent="space-around">
{/* This first ThemeProvider is the racine default theme*/}
<ThemeProvider theme={theme}>
<Button appearance="primary">Primary Theme</Button>
<ThemeProvider theme={customTheme}>
<Box>
<Button appearance="primary">Custom Theme</Button>
</Box>
</ThemeProvider>
</ThemeProvider>
</Box>
);
}