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.
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.
<Boxbg="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.
<Boxbg="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.baseform.border.baseform.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 element | Modifiers |
---|---|
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 element | Modifiers |
---|---|
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 element | Modifiers |
---|---|
button.primary.background.base | hover active |
button.primary.border.base | - |
button.primary.text.base | hover |
Secondary
Used as supplementary calls to action.
Base element | Modifiers |
---|---|
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 element | Modifiers |
---|---|
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 element | Modifiers |
---|---|
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 element | Modifiers |
---|---|
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 element | Modifiers |
---|---|
button.unstyled.background.base | - |
button.unstyled.border.base | - |
button.unstyled.text.base | hover |
Link
Base element | Modifiers |
---|---|
link.base | hover |
Text
Base element | Modifiers |
---|---|
text.headline | - |
text.subtext | - |
text.body | - |
text.inverse | - |
text.error | - |
text.background.highlight | - |
text.background.selection | #a1c2f8 |
text.decorative | - |
Icon
Base element | Modifiers |
---|---|
icon.base | inverse success warning error danger info opportunity applied positive_sentiment negative_sentiment neutral_sentiment |
Form
Base element | Modifiers |
---|---|
form.background.base | selected |
form.border.base | error warning selected |
form.placeholder.base | - |
List item
Base element | Modifiers |
---|---|
listItem.background.base | hover selected active |
listItem.border.base | - |
Overlay
Base element | Modifiers |
---|---|
overlay.background.base | - |
overlay.text.base | - |
overlay.icon.base | - |
Elevation
boxShadow: (p) => p.theme.colors.elevation.base;
Base element | Modifiers |
---|---|
elevation.base | - |
Network
Base element | Modifiers |
---|---|
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 element | Modifiers |
---|---|
dataviz.map | An object containing an ordered list of dataviz colors |
dataviz.list | An 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 prop | Key path | Value |
---|---|---|
0 | theme.space[0] | |
100 / -100 | theme.space[100] / theme.space[-100] | |
200 / -200 | theme.space[200] / theme.space[-200] | |
300 / -300 | theme.space[300] / theme.space[-300] | |
350 / -350 | theme.space[350] / theme.space[-350] | |
400 / -400 | theme.space[400] / theme.space[-400] | |
450 / -450 | theme.space[450] / theme.space[-450] | |
500 / -500 | theme.space[500] / theme.space[-500] | |
600 / -600 | theme.space[600] / theme.space[-600] |
Typography
Typography tokens encapsulate both font size and line height values.
System prop | Key path | Value |
---|---|---|
100 | theme.typography[100] | |
200 | theme.typography[200] | |
300 | theme.typography[300] | |
400 | theme.typography[400] | |
500 | theme.typography[500] | |
600 | theme.typography[600] | |
700 | theme.typography[700] | |
800 | theme.typography[800] | |
900 | theme.typography[900] | |
1000 | theme.typography[1000] | |
1100 | theme.typography[1100] | |
1200 | theme.typography[1200] |
Font weight
System prop | Key path | Value |
---|---|---|
normal | theme.fontWeights.normal | |
semibold | theme.fontWeights.semibold | |
bold | theme.fontWeights.bold | |
extrabold | theme.fontWeights.extrabold |
Border width
System prop | Key path | Value |
---|---|---|
500 | theme.borderWidths[500] | 1px |
600 | theme.borderWidths[600] | 2px |
700 | theme.borderWidths[700] | 3px |
Border radius
System prop | Key path | Value |
---|---|---|
inner | theme.radii.inner | 500 token6px |
outer | theme.radii.outer | 600 token8px |
pill | theme.radii.pill | 1000 token999999px |
Box shadow
System prop | Key path | Value |
---|---|---|
low | theme.shadows.low | 0px 2px 4px #2733333D |
medium | theme.shadows.medium | 0px 8px 16px #2733333D |
high | theme.shadows.high | 0px 16px 32px #2733333D |
Easing
System prop | Key path | Value |
---|---|---|
ease_in | theme.easing[ease_in] | cubic-bezier(.4, 0, .7, .2) |
ease_out | theme.easing[ease_out] | cubic-bezier(0, 0, .2, 1) |
ease_inout | theme.easing[ease_inout] | cubic-bezier(.4, 0, .2, 1) |
Duration
System prop | Key path | Value |
---|---|---|
fast | theme.duration[fast] | .15s |
medium | theme.duration[medium] | .3s |
slow | theme.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.
Index/name | Key path | Value |
---|---|---|
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.jssrc/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'// orimport { 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.jsimport { 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.jsimport { 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.jsimport { 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 nameconst 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 colorconst 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.jsconst overridePrimaryButtonBackground = {base: theme.colors.purple[700],hover: 'orange',active: 'green'}// Create custom themeconst 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>);}