This skill activates when customizing Chakra UI themes, implementing design systems, configuring color modes, extending component styles, or working with design tokens. It provides comprehensive guidance on theme architecture, semantic tokens, global styles, and dark mode implementation for Chakra UI v2 applications.
/plugin marketplace add Lobbi-Docs/claude/plugin install chakra-react-toolkit@claude-orchestrationThis skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill activates when customizing Chakra UI themes, implementing design systems, configuring color modes, extending component styles, or working with design tokens. It provides comprehensive guidance on theme architecture, semantic tokens, global styles, and dark mode implementation for Chakra UI v2 applications.
Use extendTheme to customize the default Chakra theme while preserving base functionality:
import { extendTheme, type ThemeConfig } from '@chakra-ui/react';
// Color mode configuration
const config: ThemeConfig = {
initialColorMode: 'light',
useSystemColorMode: false,
};
// Custom theme object
const theme = extendTheme({
config,
colors: {
brand: {
50: '#e3f2f9',
100: '#c5e4f3',
200: '#a2d4ec',
300: '#7ac1e4',
400: '#47a9da',
500: '#0088cc',
600: '#007ab8',
700: '#006ba1',
800: '#005885',
900: '#003f5e',
},
},
fonts: {
heading: `'Inter', sans-serif`,
body: `'Inter', sans-serif`,
},
fontSizes: {
xs: '0.75rem',
sm: '0.875rem',
md: '1rem',
lg: '1.125rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem',
'4xl': '2.25rem',
'5xl': '3rem',
'6xl': '3.75rem',
'7xl': '4.5rem',
'8xl': '6rem',
'9xl': '8rem',
},
breakpoints: {
base: '0em',
sm: '30em',
md: '48em',
lg: '62em',
xl: '80em',
'2xl': '96em',
},
});
export default theme;
Wrap your application with ChakraProvider and pass the custom theme:
import { ChakraProvider } from '@chakra-ui/react';
import theme from './theme';
function App() {
return (
<ChakraProvider theme={theme}>
<YourApp />
</ChakraProvider>
);
}
Define color palettes with numeric scales (50-900) for light to dark shades:
const theme = extendTheme({
colors: {
// Brand colors
brand: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
},
// Semantic colors
success: {
50: '#f0fdf4',
500: '#22c55e',
600: '#16a34a',
},
error: {
50: '#fef2f2',
500: '#ef4444',
600: '#dc2626',
},
warning: {
50: '#fffbeb',
500: '#f59e0b',
600: '#d97706',
},
// Neutral colors
gray: {
50: '#f9fafb',
100: '#f3f4f6',
200: '#e5e7eb',
300: '#d1d5db',
400: '#9ca3af',
500: '#6b7280',
600: '#4b5563',
700: '#374151',
800: '#1f2937',
900: '#111827',
},
},
});
// Usage in components
<Box bg="brand.500" color="white">Brand colored box</Box>
<Text color="gray.600">Muted text</Text>
<Button colorScheme="brand">Brand button</Button>
Use semantic tokens for color mode-aware values:
const theme = extendTheme({
semanticTokens: {
colors: {
// Text colors
'text.primary': {
default: 'gray.900',
_dark: 'gray.50',
},
'text.secondary': {
default: 'gray.600',
_dark: 'gray.400',
},
'text.muted': {
default: 'gray.500',
_dark: 'gray.500',
},
// Background colors
'bg.canvas': {
default: 'white',
_dark: 'gray.900',
},
'bg.surface': {
default: 'gray.50',
_dark: 'gray.800',
},
'bg.elevated': {
default: 'white',
_dark: 'gray.700',
},
// Border colors
'border.default': {
default: 'gray.200',
_dark: 'gray.700',
},
'border.muted': {
default: 'gray.100',
_dark: 'gray.800',
},
// Interactive colors
'interactive.default': {
default: 'brand.500',
_dark: 'brand.400',
},
'interactive.hover': {
default: 'brand.600',
_dark: 'brand.300',
},
},
},
});
// Usage - automatically adapts to color mode
<Box bg="bg.canvas" borderColor="border.default">
<Text color="text.primary">Primary text</Text>
<Text color="text.secondary">Secondary text</Text>
</Box>
Customize spacing scale for consistent padding and margin:
const theme = extendTheme({
space: {
px: '1px',
0.5: '0.125rem',
1: '0.25rem',
1.5: '0.375rem',
2: '0.5rem',
2.5: '0.625rem',
3: '0.75rem',
3.5: '0.875rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
7: '1.75rem',
8: '2rem',
9: '2.25rem',
10: '2.5rem',
12: '3rem',
14: '3.5rem',
16: '4rem',
20: '5rem',
24: '6rem',
28: '7rem',
32: '8rem',
36: '9rem',
40: '10rem',
44: '11rem',
48: '12rem',
52: '13rem',
56: '14rem',
60: '15rem',
64: '16rem',
72: '18rem',
80: '20rem',
96: '24rem',
},
});
Define font families, sizes, weights, and line heights:
const theme = extendTheme({
fonts: {
heading: `'Poppins', sans-serif`,
body: `'Inter', sans-serif`,
mono: `'JetBrains Mono', monospace`,
},
fontSizes: {
xs: '0.75rem',
sm: '0.875rem',
md: '1rem',
lg: '1.125rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem',
'4xl': '2.25rem',
'5xl': '3rem',
'6xl': '3.75rem',
},
fontWeights: {
hairline: 100,
thin: 200,
light: 300,
normal: 400,
medium: 500,
semibold: 600,
bold: 700,
extrabold: 800,
black: 900,
},
lineHeights: {
normal: 'normal',
none: 1,
shorter: 1.25,
short: 1.375,
base: 1.5,
tall: 1.625,
taller: 2,
},
letterSpacings: {
tighter: '-0.05em',
tight: '-0.025em',
normal: '0',
wide: '0.025em',
wider: '0.05em',
widest: '0.1em',
},
});
Customize elevation shadows for depth:
const theme = extendTheme({
shadows: {
xs: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
sm: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
'2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
outline: '0 0 0 3px rgba(66, 153, 225, 0.5)',
none: 'none',
// Dark mode shadows
'dark-lg': '0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -2px rgba(0, 0, 0, 0.25)',
},
});
Configure color mode behavior and provide toggle functionality:
// theme.ts
import { extendTheme, type ThemeConfig } from '@chakra-ui/react';
const config: ThemeConfig = {
initialColorMode: 'light', // 'light' | 'dark' | 'system'
useSystemColorMode: false, // Use OS preference
};
const theme = extendTheme({
config,
styles: {
global: (props) => ({
body: {
bg: props.colorMode === 'dark' ? 'gray.900' : 'white',
color: props.colorMode === 'dark' ? 'gray.50' : 'gray.900',
},
}),
},
});
export default theme;
Implement a dark mode toggle button:
import { IconButton, useColorMode, useColorModeValue } from '@chakra-ui/react';
import { MoonIcon, SunIcon } from '@chakra-ui/icons';
function ColorModeToggle() {
const { colorMode, toggleColorMode } = useColorMode();
const icon = useColorModeValue(<MoonIcon />, <SunIcon />);
return (
<IconButton
aria-label="Toggle color mode"
icon={icon}
onClick={toggleColorMode}
variant="ghost"
/>
);
}
// Alternative with manual color mode values
function ColorModeToggleAlt() {
const { toggleColorMode } = useColorMode();
const bg = useColorModeValue('gray.100', 'gray.700');
const color = useColorModeValue('gray.800', 'gray.100');
return (
<IconButton
aria-label="Toggle dark mode"
icon={<MoonIcon />}
onClick={toggleColorMode}
bg={bg}
color={color}
/>
);
}
Use useColorModeValue for component-level color mode support:
import { Box, useColorModeValue } from '@chakra-ui/react';
function Card() {
// Returns first value in light mode, second in dark mode
const bg = useColorModeValue('white', 'gray.800');
const borderColor = useColorModeValue('gray.200', 'gray.700');
const textColor = useColorModeValue('gray.900', 'gray.50');
const shadowColor = useColorModeValue('md', 'dark-lg');
return (
<Box
bg={bg}
borderColor={borderColor}
color={textColor}
shadow={shadowColor}
borderWidth="1px"
borderRadius="lg"
p={6}
>
Content adapts to color mode
</Box>
);
}
// Or use semantic tokens (preferred)
function CardWithSemanticTokens() {
return (
<Box
bg="bg.surface"
borderColor="border.default"
color="text.primary"
borderWidth="1px"
borderRadius="lg"
p={6}
>
Content with semantic tokens
</Box>
);
}
Add color mode script to prevent flash of unstyled content:
// pages/_document.tsx (Next.js)
import { ColorModeScript } from '@chakra-ui/react';
import NextDocument, { Html, Head, Main, NextScript } from 'next/document';
import theme from '../theme';
export default class Document extends NextDocument {
render() {
return (
<Html lang="en">
<Head />
<body>
<ColorModeScript initialColorMode={theme.config.initialColorMode} />
<Main />
<NextScript />
</body>
</Html>
);
}
}
// Or in index.html (Vite/CRA)
// <script>
// (function() {
// try {
// var mode = localStorage.getItem('chakra-ui-color-mode');
// if (!mode) return;
// document.documentElement.dataset.theme = mode;
// document.documentElement.style.colorScheme = mode;
// } catch (e) {}
// })();
// </script>
Customize components with single-part anatomy:
const theme = extendTheme({
components: {
Button: {
// Base styles applied to all variants
baseStyle: {
fontWeight: 'semibold',
borderRadius: 'lg',
_focus: {
boxShadow: 'outline',
},
},
// Size variants
sizes: {
sm: {
fontSize: 'sm',
px: 4,
py: 2,
},
md: {
fontSize: 'md',
px: 6,
py: 3,
},
lg: {
fontSize: 'lg',
px: 8,
py: 4,
},
},
// Style variants
variants: {
primary: {
bg: 'brand.500',
color: 'white',
_hover: {
bg: 'brand.600',
_disabled: {
bg: 'brand.500',
},
},
},
secondary: {
bg: 'gray.200',
color: 'gray.800',
_hover: {
bg: 'gray.300',
},
},
ghost: {
bg: 'transparent',
color: 'brand.500',
_hover: {
bg: 'brand.50',
},
},
},
// Default values
defaultProps: {
size: 'md',
variant: 'primary',
},
},
},
});
// Usage
<Button variant="primary" size="lg">Primary Button</Button>
<Button variant="secondary">Secondary Button</Button>
<Button variant="ghost" size="sm">Ghost Button</Button>
Customize components with multiple parts:
import { defineStyleConfig, createMultiStyleConfigHelpers } from '@chakra-ui/react';
import { cardAnatomy } from '@chakra-ui/anatomy';
const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(cardAnatomy.keys);
// Define styles for each part
const baseStyle = definePartsStyle({
container: {
bg: 'bg.surface',
borderColor: 'border.default',
borderRadius: 'lg',
},
header: {
paddingBottom: 2,
borderBottomWidth: '1px',
borderColor: 'border.muted',
},
body: {
paddingTop: 4,
},
footer: {
paddingTop: 2,
borderTopWidth: '1px',
borderColor: 'border.muted',
},
});
// Define variants
const variants = {
elevated: definePartsStyle({
container: {
shadow: 'lg',
borderWidth: '0',
},
}),
outline: definePartsStyle({
container: {
borderWidth: '1px',
shadow: 'none',
},
}),
filled: definePartsStyle({
container: {
bg: 'gray.100',
_dark: {
bg: 'gray.700',
},
},
}),
};
export const cardTheme = defineMultiStyleConfig({
baseStyle,
variants,
defaultProps: {
variant: 'elevated',
},
});
// Apply to theme
const theme = extendTheme({
components: {
Card: cardTheme,
},
});
Override default props for components:
const theme = extendTheme({
components: {
Button: {
defaultProps: {
size: 'lg',
colorScheme: 'brand',
},
},
Input: {
defaultProps: {
size: 'md',
variant: 'filled',
focusBorderColor: 'brand.500',
},
},
Heading: {
defaultProps: {
size: 'xl',
},
},
},
});
Define global styles that apply throughout your application:
const theme = extendTheme({
styles: {
global: (props) => ({
// Root styles
'html, body': {
fontSize: 'md',
color: props.colorMode === 'dark' ? 'gray.50' : 'gray.900',
bg: props.colorMode === 'dark' ? 'gray.900' : 'white',
lineHeight: 'tall',
},
// Typography
'h1, h2, h3, h4, h5, h6': {
fontFamily: 'heading',
fontWeight: 'bold',
},
// Links
a: {
color: props.colorMode === 'dark' ? 'brand.300' : 'brand.500',
_hover: {
textDecoration: 'underline',
},
},
// Selection
'::selection': {
bg: props.colorMode === 'dark' ? 'brand.700' : 'brand.200',
color: props.colorMode === 'dark' ? 'white' : 'gray.900',
},
// Scrollbar (webkit)
'::-webkit-scrollbar': {
width: '12px',
},
'::-webkit-scrollbar-track': {
bg: props.colorMode === 'dark' ? 'gray.800' : 'gray.100',
},
'::-webkit-scrollbar-thumb': {
bg: props.colorMode === 'dark' ? 'gray.600' : 'gray.400',
borderRadius: 'full',
border: '3px solid',
borderColor: props.colorMode === 'dark' ? 'gray.800' : 'gray.100',
},
}),
},
});
Define reusable layer styles for common patterns:
const theme = extendTheme({
layerStyles: {
card: {
bg: 'bg.surface',
borderRadius: 'lg',
borderWidth: '1px',
borderColor: 'border.default',
shadow: 'sm',
},
cardHover: {
bg: 'bg.surface',
borderRadius: 'lg',
borderWidth: '1px',
borderColor: 'border.default',
shadow: 'sm',
transition: 'all 0.2s',
_hover: {
shadow: 'lg',
transform: 'translateY(-2px)',
},
},
navbar: {
bg: 'bg.elevated',
borderBottom: '1px',
borderColor: 'border.default',
shadow: 'sm',
},
},
});
// Usage
<Box layerStyle="card" p={6}>
Card content
</Box>
<Box layerStyle="cardHover" p={6}>
Interactive card
</Box>
Define reusable text styles:
const theme = extendTheme({
textStyles: {
h1: {
fontSize: ['4xl', '5xl', '6xl'],
fontWeight: 'bold',
lineHeight: 'shorter',
letterSpacing: 'tight',
},
h2: {
fontSize: ['3xl', '4xl', '5xl'],
fontWeight: 'bold',
lineHeight: 'short',
letterSpacing: 'tight',
},
body: {
fontSize: 'md',
lineHeight: 'tall',
},
caption: {
fontSize: 'sm',
lineHeight: 'base',
color: 'text.secondary',
},
},
});
// Usage
<Text textStyle="h1">Heading</Text>
<Text textStyle="body">Body text</Text>
<Text textStyle="caption">Caption text</Text>
Support multiple brands with theme switching:
// themes/brand-a.ts
export const brandATheme = extendTheme({
colors: {
brand: { /* Brand A colors */ },
},
});
// themes/brand-b.ts
export const brandBTheme = extendTheme({
colors: {
brand: { /* Brand B colors */ },
},
});
// App.tsx
import { useState } from 'react';
import { ChakraProvider } from '@chakra-ui/react';
function App() {
const [currentTheme, setCurrentTheme] = useState(brandATheme);
return (
<ChakraProvider theme={currentTheme}>
<YourApp onThemeChange={setCurrentTheme} />
</ChakraProvider>
);
}
Configure responsive font sizes globally:
const theme = extendTheme({
styles: {
global: {
html: {
fontSize: { base: '14px', md: '16px', lg: '18px' },
},
},
},
textStyles: {
responsive: {
fontSize: { base: 'sm', md: 'md', lg: 'lg' },
},
},
});
Use these theming patterns to create consistent, maintainable design systems that scale with your application needs and support multiple color modes seamlessly.