Theming
Twigs ships with maximum flexibility to customize the look and feel of your application.
Theme Setup
Twigs uses Stitches as its styling engine. The theme system consists of:
- Default Theme (
stitches.config.ts) - Contains all default theme values and configuration functions for initial setup. - ThemeProvider - Applies and merges custom themes with defaults.
ThemeProvider Architecture
The ThemeProvider automatically:
- Runs global styles on mount - Applies CSS reset and base styles (box-sizing, font-family, etc.)
- Merges themes - Combines your custom theme with defaults
- Applies theme - Adds merged theme as CSS class to document root
// Simplified ThemeProvider behavior
const mergeThemes = (outerTheme: any, theme: any) => {
return { ...outerTheme, ...theme };
};
useEffect(() => {
globalStyles(); // Runs once on mount - applies CSS reset & base styles
}, []);Key behaviors:
- Global styles applied automatically (CSS reset, box-sizing, font-family)
- Custom values override defaults at the same key level
- Nested objects are merged (not replaced)
- Theme is applied as a CSS class to document root
- Changes update all components automatically
Custom themes merge with defaults - only specified keys override:
// Default theme
{ colors: { primary: "#2E666D", secondary: "#363A43" } }
// Custom theme
{ colors: { primary: "#FF5733" } }
// Result: primary overridden, secondary preserved
{ colors: { primary: "#FF5733", secondary: "#363A43" } }Basic Setup
Wrap your application with ThemeProvider:
import { ThemeProvider, defaultTheme } from '@sparrowengg/twigs-react'
function App() {
return (
<ThemeProvider theme={defaultTheme}>
<YourApp />
</ThemeProvider>
)
}For the best experience, wrap ThemeProvider in your main.jsx or main.tsx file at the root of your application. This ensures all components have access to the theme.
Customizing the Theme
Using Custom File (Recommended)
Create a twigs.config.js or twigs.config.ts file in your project root:
export default {
theme: {
extends: {
// overridding the default colors
colors: {
primary500: "blue",
secondary500: "green",
},
// overridding the default font
fonts: {
body: "DM Sans, sans-serif",
heading: "DM Sans, sans-serif",
},
}
}
};Make sure to import your custom fonts (e.g., DM Sans) in your project via Google Fonts, a CDN, or local files. The font will not apply unless it's properly loaded in your application.
Import and use in ThemeProvider:
import { ThemeProvider } from '@sparrowengg/twigs-react';
import twigsConfig from '../twigs.config.js';
function App() {
return (
<ThemeProvider theme={twigsConfig.theme.extends}>
<YourApp />
</ThemeProvider>
);
}Benefits: Better organization, IntelliSense support, version control friendly.
Direct Theme Prop
Pass theme object directly:
import { ThemeProvider } from '@sparrowengg/twigs-react';
const customTheme = {
colors: {
primary: "#2E666D",
},
fontSizes: {
md: "1.125rem",
},
};
function App() {
return (
<ThemeProvider theme={customTheme}>
<YourApp />
</ThemeProvider>
);
}Dark Mode Configuration
Add dark mode support using the createTheme function, which generates a theme class that can be applied to override specific tokens while inheriting others from your default theme.
Add Dark Theme
Extend your twigs.config by altering the default export to include dark theme configuration
import { createTheme } from '@sparrowengg/twigs-react';
const config = {
theme: {
extends: {
colors: {
primary500: "blue",
secondary500: "green",
},
fonts: {
body: "DM Sans, sans-serif",
},
},
dark: {
colors: {
primary500: "#5CB5BD",
secondary500: "#A3AEBD",
// Override colors for dark mode
},
}
}
};
export default config;
export const darkTheme = createTheme('dark-theme', config.theme.dark);Apply Theme class
Use ThemeProvider for your default theme and apply the darkTheme class conditionally when needed.
import config, { darkTheme } from '../twigs.config.js';
import { useState } from "react";
function App() {
const [isDark,setIsDark] = useState(false);
return (
<div className={isDark ? darkTheme : ''}>
<yourComponents />
</div>
);
}Apply the dark theme class in your App.tsx or App.jsx file to ensure consistent theming across your entire application.
How it works:
ThemeProviderapplies your custom theme via thethemeprop.- Dark theme class is applied to the wrapper div when
isDarkis true. - Both work together: custom theme + dark mode overrides.
- Only tokens defined in dark theme override; others use your custom/default theme.
See the Stitches Custom Theming documentation for complete details regarding the dark mode configuration.
Theme Variables
Reference theme variables using the $ prefix in the css prop:
<Text
css={{
fontSize:"$lg",
lineHeight:"$lg",
fontWeight:"$5",
padding:"$5",
borderWidth:"$xs",
borderStyle:"solid",
borderRadius:"$md"
}}
>
Twigs
</Text>Reference
| Category | Example | Keys |
|---|---|---|
| Colors | $primary500, $white900 | Named |
| Spacing | $4, $10, $24 | Numeric (1-50) |
| Font Sizes | $sm, $md, $xl | Named |
| Font Weights | $4, $6, $7 | Numeric (1-9) |
| Border Radius | $sm, $md, $pill | Named |
| Sizes | $10, $20, $25 | Numeric (1-34) |
Breakpoints
Twigs includes pre-configured breakpoints for responsive design. Use them with the @ prefix in your css prop:
<Box
css={{
padding: '$4',
'@screen-md': {
padding: '$8',
},
'@screen-lg': {
padding: '$12',
},
}}
>
Responsive Content
</Box>Available breakpoints:
@screen-xxs- 320px@screen-xs- 480px@screen-sm- 640px@screen-md- 768px@screen-lg- 1024px@screen-xl- 1280px@screen-2xl- 1536px
Breakpoints are pre-configured in the Twigs setup and cannot be overridden through ThemeProvider. For custom breakpoints, use standard CSS media queries directly in your css prop.
Scale-Prefixed Tokens
By default, theme tokens are automatically mapped to their corresponding CSS properties. For example, $primary500 works in color-related properties, and $6 works in spacing properties.
When you need to explicitly target a token from a specific scale (e.g., using a color token in a non-color property), use scale-prefixed syntax:
<Box
css={{
marginTop: '$sizes$10', // Use size token for margin
boxShadow: '0 0 0 2px $colors$primary500', // Use color token in shadow
border: '1px solid $colors$secondary500', // Explicit color scale
}}
>
Content
</Box>Scale-prefixed syntax: $scaleName$tokenName
$colors$primary500- Explicitly use color token$space$6- Explicitly use spacing token$sizes$10- Explicitly use size token
For complete property mapping details, see the Stitches Property Mapping documentation.
Color Opacity Utils
Twigs provides utility functions to apply opacity to theme colors. These utils convert theme colors to RGBA format with your specified opacity:
<Box
css={{
backgroundColorOpacity: ['$primary500', 0.1], // 10% opacity background
colorOpacity: ['$primary800', 0.8], // 80% opacity text
borderColorOpacity: ['$secondary500', 0.5], // 50% opacity border
}}
>
Content with opacity
</Box>Available utils:
backgroundColorOpacity: [colorToken, opacity]- Apply opacity to background colorcolorOpacity: [colorToken, opacity]- Apply opacity to text colorborderColorOpacity: [colorToken, opacity]- Apply opacity to border color
Opacity value: Number between 0 (transparent) and 1 (opaque).
Advanced Styling
When modifying default colors or styles of Twigs components, you may need to use !important to override component defaults that have higher CSS specificity:
<Button
css={{
backgroundColor: '$primary500',
'&:hover': {
backgroundColor: '$primary600 !important',
},
'&:focus': {
outline: '2px solid $primary300',
},
'@screen-md': {
padding: '$8 $12',
},
}}
>
Submit
</Button>Best practice: Try without !important first. Only add it when the style isn't being applied due to specificity conflicts.
Practical Example
Server-Side Rendering
Twigs supports server-side rendering (SSR) through the getCssText function, which extracts all CSS needed for hydration.
Basic Setup
Here's an example of SSR with Next.js
import { Html, Head, Main, NextScript } from "next/document";
import { getCssText } from "@sparrowengg/twigs-react";
export default function Document() {
return (
<Html lang="en">
<Head>
<style
id="stitches"
dangerouslySetInnerHTML={{ __html: getCssText() }}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}Important: Always include id="stitches" on the style tag for optimal hydration.
How it works:
getCssText()returns all CSS generated by Stitches- Styles are injected server-side to prevent FOUC (Flash of Unstyled Content)
Complete Theme Reference
Colors
Click any color swatch to copy its token name, Use these values in the css prop to style your components.
primary
secondary
accent
warning
highlight
positive
negative
neutral
black
white
Font Sizes
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
Font Weights
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
Line Heights
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
Border Widths and Radius
Border Widths
Border Radius
Spacing
Space values for padding, margin, gap (numeric keys 1-50):
| Key | Value | px Value | Example |
|---|---|---|---|
| 1 | 0.125rem | 2px | |
| 2 | 0.25rem | 4px | |
| 3 | 0.375rem | 6px | |
| 4 | 0.5rem | 8px | |
| 5 | 0.625rem | 10px | |
| 6 | 0.75rem | 12px | |
| 7 | 0.875rem | 14px | |
| 8 | 1rem | 16px | |
| 9 | 1.125rem | 18px | |
| 10 | 1.25rem | 20px | |
| 11 | 1.375rem | 22px | |
| 12 | 1.5rem | 24px | |
| 13 | 1.625rem | 26px | |
| 14 | 1.75rem | 28px | |
| 15 | 1.875rem | 30px | |
| 16 | 2rem | 32px | |
| 17 | 2.125rem | 34px | |
| 18 | 2.25rem | 36px | |
| 19 | 2.375rem | 38px | |
| 20 | 2.5rem | 40px | |
| 21 | 2.625rem | 42px | |
| 22 | 2.75rem | 44px | |
| 23 | 2.875rem | 46px | |
| 24 | 3rem | 48px | |
| 25 | 3.125rem | 50px | |
| 26 | 3.25rem | 52px | |
| 27 | 3.375rem | 54px | |
| 28 | 3.5rem | 56px | |
| 29 | 3.625rem | 58px | |
| 30 | 3.75rem | 60px | |
| 31 | 3.875rem | 62px | |
| 32 | 4rem | 64px | |
| 33 | 4.125rem | 66px | |
| 34 | 4.25rem | 68px | |
| 35 | 4.375rem | 70px | |
| 36 | 4.5rem | 72px | |
| 37 | 4.625rem | 74px | |
| 38 | 4.75rem | 76px | |
| 39 | 4.875rem | 78px | |
| 40 | 5rem | 80px | |
| 41 | 5.125rem | 82px | |
| 42 | 5.25rem | 84px | |
| 43 | 5.375rem | 86px | |
| 44 | 5.5rem | 88px | |
| 45 | 5.625rem | 90px | |
| 46 | 5.75rem | 92px | |
| 47 | 5.875rem | 94px | |
| 48 | 6rem | 96px | |
| 49 | 6.125rem | 98px | |
| 50 | 6.25rem | 100px |
Sizing
Size tokens for height, width, dimensions (numeric keys 1-34):
| Key | Value | Example |
|---|---|---|
| 1 | 4px | |
| 2 | 8px | |
| 3 | 12px | |
| 4 | 16px | |
| 5 | 20px | |
| 6 | 24px | |
| 7 | 28px | |
| 8 | 32px | |
| 9 | 36px | |
| 10 | 40px | |
| 11 | 44px | |
| 12 | 48px | |
| 13 | 52px | |
| 14 | 56px | |
| 15 | 60px | |
| 16 | 64px | |
| 17 | 68px | |
| 18 | 72px | |
| 19 | 76px | |
| 20 | 80px | |
| 21 | 84px | |
| 22 | 88px | |
| 23 | 92px | |
| 24 | 96px | |
| 25 | 100px | |
| 26 | 104px | |
| 27 | 108px | |
| 28 | 112px | |
| 29 | 116px | |
| 30 | 120px | |
| 31 | 124px | |
| 32 | 128px | |
| 33 | 132px | |
| 34 | 136px |
Default Theme Object
"colors": {
"primary": "#2E666D",
"secondary": "#363A43",
"accent50": "#F3F3FF",
"accent100": "#EAE9FE",
"accent200": "#D7D6FE",
"accent300": "#B9B5FD",
"accent400": "#978CF9",
"accent500": "#7158F5",
"accent600": "#623BEC",
"accent700": "#5329D8",
"accent800": "#4622B5",
"accent900": "#3B1E94",
"primary50": "#E6F5F6",
"primary100": "#B8E1E5",
"primary200": "#8ACCD2",
"primary300": "#5CB5BD",
"primary400": "#2E9CA6",
"primary500": "#00828D",
"primary600": "#006B74",
"primary700": "#00555C",
"primary800": "#003E43",
"primary900": "#00272A",
"warning50": "#FFF6EF",
"warning100": "#FEEAC7",
"warning200": "#FDD28A",
"warning300": "#FCBD4F",
"warning400": "#FBAB24",
"warning500": "#F59E0B",
"warning600": "#DB8D06",
"warning700": "#B47409",
"warning800": "#92610E",
"warning900": "#78510F",
"highlight50": "#FFFCDA",
"highlight100": "#FFF7AD",
"highlight200": "#FFF27D",
"highlight300": "#FFED4B",
"highlight400": "#FFE81A",
"highlight500": "#E6CF00",
"highlight600": "#B3A100",
"highlight700": "#807300",
"highlight800": "#786B03",
"highlight900": "#6A5F00",
"positive50": "#F4FAF1",
"positive100": "#E8F4E3",
"positive200": "#D4E8CA",
"positive300": "#A8D291",
"positive400": "#67B034",
"positive500": "#5EA130",
"positive600": "#55932A",
"positive700": "#4C8425",
"positive800": "#437720",
"positive900": "#3C691C",
"secondary50": "#F4F6F7",
"secondary100": "#E2E6EB",
"secondary200": "#C9CFD8",
"secondary300": "#A3AEBD",
"secondary400": "#76859A",
"secondary500": "#64748B",
"secondary600": "#4E596C",
"secondary700": "#444B5A",
"secondary800": "#3D424D",
"secondary900": "#363A43",
"negative50": "#FFF6F3",
"negative100": "#FDEDE8",
"negative200": "#FFDAD0",
"negative300": "#FFB4A1",
"negative400": "#FA7659",
"negative500": "#F65633",
"negative600": "#E75030",
"negative700": "#D14729",
"negative800": "#BC4024",
"negative900": "#A9371E",
"neutral50": "#F8F8F8",
"neutral100": "#F1F1F1",
"neutral200": "#E2E2E2",
"neutral300": "#C6C6C6",
"neutral400": "#9E9E9E",
"neutral500": "#919191",
"neutral600": "#848484",
"neutral700": "#757575",
"neutral800": "#575757",
"neutral900": "#111111",
"black50": "#0000000A",
"black100": "#00000014",
"black200": "#0000001A",
"black300": "#00000026",
"black400": "#00000033",
"black500": "#0000004D",
"black600": "#00000080",
"black700": "#000000B2",
"black800": "#000000CC",
"black900": "#000000",
"white50": "#FFFFFF0D",
"white100": "#FFFFFF14",
"white200": "#FFFFFF1A",
"white300": "#FFFFFF26",
"white400": "#FFFFFF33",
"white500": "#FFFFFF4D",
"white600": "#FFFFFF80",
"white700": "#FFFFFFB2",
"white800": "#FFFFFFCC",
"white900": "#FFFFFF"
},
"space": {
1: "0.125rem",
2: "0.25rem",
3: "0.375rem",
4: "0.5rem",
5: "0.625rem",
6: "0.75rem",
7: "0.875rem",
8: "1rem",
9: "1.125rem",
10: "1.25rem",
11: "1.375rem",
12: "1.5rem",
13: "1.625rem",
14: "1.75rem",
15: "1.875rem",
16: "2rem",
17: "2.125rem",
18: "2.25rem",
19: "2.375rem",
20: "2.5rem",
21: "2.625rem",
22: "2.75rem",
23: "2.875rem",
24: "3rem",
25: "3.125rem",
26: "3.25rem",
27: "3.375rem",
28: "3.5rem",
29: "3.625rem",
30: "3.75rem",
31: "3.875rem",
32: "4rem",
33: "4.125rem",
34: "4.25rem",
35: "4.375rem",
36: "4.5rem",
37: "4.625rem",
38: "4.75rem",
39: "4.875rem",
40: "5rem",
41: "5.125rem",
42: "5.25rem",
43: "5.375rem",
44: "5.5rem",
45: "5.625rem",
46: "5.75rem",
47: "5.875rem",
48: "6rem",
49: "6.125rem",
50: "6.25rem"
},
"fontSizes": {
"xxs": "0.625rem",
"xs": "0.75rem",
"sm": "0.875rem",
"md": "1rem",
"lg": "1.2rem",
"xl": "1.44rem",
"2xl": "1.728rem",
"3xl": "2.074rem",
"4xl": "2.488rem",
"5xl": "2.986rem"
},
"fonts": {
"body": "sytem-ui",
"heading": "sans-serif"
},
"fontWeights": {
1: "100",
2: "200",
3: "300",
4: "400",
5: "500",
6: "600",
7: "700",
8: "800",
9: "900"
},
"lineHeights": {
"xxs": "0.75rem",
"xs": "1rem",
"sm": "1.25rem",
"md": "1.5rem",
"lg": "1.75rem",
"xl": "2rem",
"2xl": "2.5rem",
"3xl": "3rem",
"4xl": "4rem"
},
"letterSpacings": {},
"sizes": {
1: "4px",
2: "8px",
3: "12px",
4: "16px",
5: "20px",
6: "24px",
7: "28px",
8: "32px",
9: "36px",
10: "40px",
11: "44px",
12: "48px",
13: "52px",
14: "56px",
15: "60px",
16: "64px",
17: "68px",
18: "72px",
19: "76px",
20: "80px",
21: "84px",
22: "88px",
23: "92px",
24: "96px",
25: "100px",
26: "104px",
27: "108px",
28: "112px",
29: "116px",
30: "120px",
31: "124px",
32: "128px",
33: "132px",
34: "136px"
},
"borderWidths": {
"xs": "1px",
"sm": "2px",
"md": "3px",
"lg": "4px",
"xl": "5px"
},
"borderStyles": {},
"radii": {
"none": "0px",
"xs": "0.125rem",
"sm": "0.25rem",
"md": "0.375rem",
"lg": "0.5rem",
"xl": "0.75rem",
"2xl": "1rem",
"3xl": "1.25rem",
"4xl": "1.5rem",
"round": "50%",
"pill": "9999px"
},
"shadows": {
"sm": "0px 5px 15px rgba(0, 0, 0, 0.04)"
},
"zIndices": {},
"transitions": {
1: "0.1s",
2: "0.2s",
3: "0.3s"
}