Chakra UI - Kompletny Przewodnik po Dostępnej Bibliotece React
Czym jest Chakra UI?
Chakra UI to modularna biblioteka React zaprojektowana z myślą o dostępności (a11y) i developer experience. Wyróżnia się unikalnym podejściem do stylowania - zamiast pisać CSS, używasz props do definiowania stylów bezpośrednio na komponentach. Dzięki temu kod jest czytelniejszy, szybszy do napisania i łatwiejszy do utrzymania.
Chakra została stworzona z myślą o kompozycji - każdy komponent jest modułowy i może być łatwo rozszerzony lub dostosowany. Wszystkie komponenty są zgodne z WAI-ARIA, co oznacza, że Twoja aplikacja będzie dostępna dla wszystkich użytkowników.
Dlaczego Chakra UI?
Kluczowe zalety Chakra UI
- Dostępność (a11y) - Wszystkie komponenty są zgodne z WAI-ARIA
- Style Props - Stylowanie przez props zamiast CSS
- Kompozycja - Modularne komponenty łatwe do rozszerzenia
- Dark Mode - Wbudowane wsparcie z useColorMode
- Responsywność - Responsywne style przez array/object syntax
- TypeScript - Pełne wsparcie z autocomplete
- Theming - Rozbudowany system tematów
Chakra UI vs inne biblioteki
| Cecha | Chakra UI | Mantine | MUI | Tailwind |
|---|---|---|---|---|
| Podejście | Style props | CSS Modules | CSS-in-JS | Utility classes |
| Dostępność | Wbudowana | Wbudowana | Wbudowana | Ręczna |
| Dark mode | useColorMode | useColorScheme | ThemeProvider | Ręczne |
| Bundle size | 30-100KB | 40-150KB | 100-300KB | 10-30KB |
| Customizacja | Wysoka | Wysoka | Średnia | Pełna |
| Learning curve | Niska | Średnia | Wysoka | Średnia |
Instalacja i konfiguracja
Instalacja
# npm
npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion
# yarn
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion
# pnpm
pnpm add @chakra-ui/react @emotion/react @emotion/styled framer-motionProvider setup
// app/providers.tsx (Next.js App Router)
'use client'
import { ChakraProvider, extendTheme } from '@chakra-ui/react'
const theme = extendTheme({
// Custom theme configuration
})
export function Providers({ children }: { children: React.ReactNode }) {
return <ChakraProvider theme={theme}>{children}</ChakraProvider>
}// app/layout.tsx
import { Providers } from './providers'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="pl">
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}Dla Pages Router (Next.js)
// pages/_app.tsx
import { ChakraProvider } from '@chakra-ui/react'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
return (
<ChakraProvider>
<Component {...pageProps} />
</ChakraProvider>
)
}Style Props - serce Chakra UI
Podstawy Style Props
import { Box, Text, Flex, Stack } from '@chakra-ui/react'
function StylePropsDemo() {
return (
<>
{/* Spacing (margin, padding) */}
<Box m={4} p={6}>Margin 16px, Padding 24px</Box>
<Box mt={2} mb={4} px={8}>Top margin 8px, Bottom 16px, Horizontal padding 32px</Box>
{/* Colors */}
<Box bg="blue.500" color="white">Blue background, white text</Box>
<Box bg="gray.100" color="gray.800">Gray shades</Box>
<Box bgGradient="linear(to-r, blue.500, purple.500)">Gradient</Box>
{/* Typography */}
<Text fontSize="xl" fontWeight="bold">Extra large bold</Text>
<Text fontSize="sm" color="gray.500">Small gray text</Text>
<Text textAlign="center" textTransform="uppercase">Centered uppercase</Text>
{/* Borders */}
<Box border="1px" borderColor="gray.200" borderRadius="md">Border</Box>
<Box borderWidth="2px" borderStyle="dashed" borderColor="blue.500">Dashed border</Box>
{/* Layout */}
<Box w="100%" h="200px">Full width, 200px height</Box>
<Box maxW="container.md" mx="auto">Centered container</Box>
{/* Flexbox */}
<Flex justify="space-between" align="center" gap={4}>
<Box>Item 1</Box>
<Box>Item 2</Box>
<Box>Item 3</Box>
</Flex>
{/* Stack (simplified flex) */}
<Stack spacing={4} direction="row">
<Box>Item 1</Box>
<Box>Item 2</Box>
</Stack>
{/* Position */}
<Box position="relative">
<Box position="absolute" top={0} right={0}>Positioned</Box>
</Box>
{/* Shadow */}
<Box shadow="md">Medium shadow</Box>
<Box shadow="lg">Large shadow</Box>
<Box shadow="2xl">Extra large shadow</Box>
</>
)
}Responsywne style
import { Box, Text, Flex, SimpleGrid } from '@chakra-ui/react'
function ResponsiveDemo() {
return (
<>
{/* Array syntax (mobile-first) */}
<Box
fontSize={['sm', 'md', 'lg', 'xl']}
// sm (base), md (480px), lg (768px), xl (992px)
>
Responsive font size
</Box>
{/* Object syntax */}
<Box
fontSize={{ base: 'sm', md: 'lg', xl: '2xl' }}
p={{ base: 2, md: 4, lg: 8 }}
bg={{ base: 'blue.100', md: 'green.100', lg: 'purple.100' }}
>
Object syntax
</Box>
{/* Responsive Flex direction */}
<Flex
direction={{ base: 'column', md: 'row' }}
gap={4}
>
<Box flex={1}>Sidebar</Box>
<Box flex={3}>Main content</Box>
</Flex>
{/* SimpleGrid with responsive columns */}
<SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} spacing={4}>
<Box bg="gray.100" p={4}>Card 1</Box>
<Box bg="gray.100" p={4}>Card 2</Box>
<Box bg="gray.100" p={4}>Card 3</Box>
</SimpleGrid>
{/* Hide/Show on breakpoints */}
<Box display={{ base: 'none', md: 'block' }}>
Visible only on md and up
</Box>
<Box display={{ base: 'block', md: 'none' }}>
Visible only on mobile
</Box>
</>
)
}Pseudo styles
import { Box, Button } from '@chakra-ui/react'
function PseudoDemo() {
return (
<>
{/* Hover */}
<Box
bg="blue.500"
_hover={{ bg: 'blue.600', transform: 'scale(1.05)' }}
transition="all 0.2s"
>
Hover me
</Box>
{/* Focus */}
<Button
_focus={{ boxShadow: 'outline', outline: 'none' }}
_focusVisible={{ ring: 2, ringColor: 'blue.500' }}
>
Focus me
</Button>
{/* Active */}
<Button
_active={{ bg: 'blue.700', transform: 'scale(0.98)' }}
>
Click me
</Button>
{/* Disabled */}
<Button
isDisabled
_disabled={{ opacity: 0.5, cursor: 'not-allowed' }}
>
Disabled
</Button>
{/* Before/After */}
<Box
position="relative"
_before={{
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '4px',
bg: 'blue.500',
}}
>
Box with top border
</Box>
{/* Dark mode specific */}
<Box
bg="white"
color="gray.800"
_dark={{ bg: 'gray.800', color: 'white' }}
>
Light/dark mode aware
</Box>
</>
)
}Komponenty
Button
import { Button, IconButton, ButtonGroup, Stack } from '@chakra-ui/react'
import { AddIcon, DeleteIcon, SettingsIcon } from '@chakra-ui/icons'
function ButtonDemo() {
return (
<Stack spacing={4}>
{/* Variants */}
<ButtonGroup spacing={2}>
<Button colorScheme="blue">Solid (default)</Button>
<Button colorScheme="blue" variant="outline">Outline</Button>
<Button colorScheme="blue" variant="ghost">Ghost</Button>
<Button colorScheme="blue" variant="link">Link</Button>
</ButtonGroup>
{/* Color schemes */}
<ButtonGroup spacing={2}>
<Button colorScheme="gray">Gray</Button>
<Button colorScheme="red">Red</Button>
<Button colorScheme="green">Green</Button>
<Button colorScheme="blue">Blue</Button>
<Button colorScheme="teal">Teal</Button>
<Button colorScheme="purple">Purple</Button>
</ButtonGroup>
{/* Sizes */}
<ButtonGroup spacing={2}>
<Button size="xs">Extra small</Button>
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
</ButtonGroup>
{/* States */}
<ButtonGroup spacing={2}>
<Button isLoading>Loading</Button>
<Button isLoading loadingText="Saving...">With text</Button>
<Button isDisabled>Disabled</Button>
</ButtonGroup>
{/* With icons */}
<ButtonGroup spacing={2}>
<Button leftIcon={<AddIcon />} colorScheme="blue">
Add item
</Button>
<Button rightIcon={<DeleteIcon />} colorScheme="red" variant="outline">
Delete
</Button>
</ButtonGroup>
{/* Icon buttons */}
<ButtonGroup spacing={2}>
<IconButton
aria-label="Add"
icon={<AddIcon />}
colorScheme="blue"
/>
<IconButton
aria-label="Settings"
icon={<SettingsIcon />}
variant="outline"
/>
<IconButton
aria-label="Delete"
icon={<DeleteIcon />}
colorScheme="red"
isRound
/>
</ButtonGroup>
</Stack>
)
}Form Controls
import {
FormControl,
FormLabel,
FormErrorMessage,
FormHelperText,
Input,
InputGroup,
InputLeftAddon,
InputRightElement,
Textarea,
Select,
Checkbox,
CheckboxGroup,
Radio,
RadioGroup,
Switch,
Slider,
SliderTrack,
SliderFilledTrack,
SliderThumb,
NumberInput,
NumberInputField,
NumberInputStepper,
NumberIncrementStepper,
NumberDecrementStepper,
PinInput,
PinInputField,
Stack,
HStack,
Button,
} from '@chakra-ui/react'
import { useState } from 'react'
import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons'
function FormDemo() {
const [showPassword, setShowPassword] = useState(false)
const [email, setEmail] = useState('')
const isError = email === ''
return (
<Stack spacing={6} maxW="md">
{/* Basic Input */}
<FormControl>
<FormLabel>Name</FormLabel>
<Input placeholder="Enter your name" />
<FormHelperText>We'll never share your name.</FormHelperText>
</FormControl>
{/* Input with error */}
<FormControl isInvalid={isError}>
<FormLabel>Email</FormLabel>
<Input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
/>
{isError ? (
<FormErrorMessage>Email is required.</FormErrorMessage>
) : (
<FormHelperText>Enter your email address.</FormHelperText>
)}
</FormControl>
{/* Password with show/hide */}
<FormControl>
<FormLabel>Password</FormLabel>
<InputGroup>
<Input
type={showPassword ? 'text' : 'password'}
placeholder="Enter password"
/>
<InputRightElement>
<Button
size="sm"
variant="ghost"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <ViewOffIcon /> : <ViewIcon />}
</Button>
</InputRightElement>
</InputGroup>
</FormControl>
{/* Input with addon */}
<FormControl>
<FormLabel>Website</FormLabel>
<InputGroup>
<InputLeftAddon>https://</InputLeftAddon>
<Input placeholder="mysite.com" />
</InputGroup>
</FormControl>
{/* Textarea */}
<FormControl>
<FormLabel>Description</FormLabel>
<Textarea placeholder="Enter description" resize="vertical" />
</FormControl>
{/* Select */}
<FormControl>
<FormLabel>Country</FormLabel>
<Select placeholder="Select country">
<option value="pl">Poland</option>
<option value="de">Germany</option>
<option value="us">United States</option>
</Select>
</FormControl>
{/* Checkbox */}
<FormControl>
<Checkbox colorScheme="blue">
I agree to terms and conditions
</Checkbox>
</FormControl>
{/* Checkbox group */}
<FormControl>
<FormLabel>Interests</FormLabel>
<CheckboxGroup colorScheme="blue" defaultValue={['react']}>
<Stack spacing={2}>
<Checkbox value="react">React</Checkbox>
<Checkbox value="vue">Vue</Checkbox>
<Checkbox value="angular">Angular</Checkbox>
</Stack>
</CheckboxGroup>
</FormControl>
{/* Radio group */}
<FormControl>
<FormLabel>Payment method</FormLabel>
<RadioGroup defaultValue="card">
<Stack direction="row" spacing={4}>
<Radio value="card">Credit Card</Radio>
<Radio value="paypal">PayPal</Radio>
<Radio value="bank">Bank Transfer</Radio>
</Stack>
</RadioGroup>
</FormControl>
{/* Switch */}
<FormControl display="flex" alignItems="center">
<FormLabel mb={0}>Enable notifications?</FormLabel>
<Switch colorScheme="blue" />
</FormControl>
{/* Slider */}
<FormControl>
<FormLabel>Volume</FormLabel>
<Slider defaultValue={30} min={0} max={100}>
<SliderTrack>
<SliderFilledTrack />
</SliderTrack>
<SliderThumb />
</Slider>
</FormControl>
{/* Number input */}
<FormControl>
<FormLabel>Quantity</FormLabel>
<NumberInput defaultValue={1} min={1} max={20}>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</FormControl>
{/* PIN input */}
<FormControl>
<FormLabel>Verification code</FormLabel>
<HStack>
<PinInput>
<PinInputField />
<PinInputField />
<PinInputField />
<PinInputField />
</PinInput>
</HStack>
</FormControl>
</Stack>
)
}Modal
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalFooter,
ModalBody,
ModalCloseButton,
useDisclosure,
Button,
FormControl,
FormLabel,
Input,
VStack,
} from '@chakra-ui/react'
import { useRef } from 'react'
function ModalDemo() {
const { isOpen, onOpen, onClose } = useDisclosure()
const initialRef = useRef<HTMLInputElement>(null)
return (
<>
<Button onClick={onOpen} colorScheme="blue">
Open Modal
</Button>
<Modal
isOpen={isOpen}
onClose={onClose}
initialFocusRef={initialRef}
isCentered
>
<ModalOverlay />
<ModalContent>
<ModalHeader>Create your account</ModalHeader>
<ModalCloseButton />
<ModalBody>
<VStack spacing={4}>
<FormControl>
<FormLabel>Name</FormLabel>
<Input ref={initialRef} placeholder="Enter your name" />
</FormControl>
<FormControl>
<FormLabel>Email</FormLabel>
<Input type="email" placeholder="you@example.com" />
</FormControl>
</VStack>
</ModalBody>
<ModalFooter>
<Button variant="ghost" mr={3} onClick={onClose}>
Cancel
</Button>
<Button colorScheme="blue" onClick={onClose}>
Create
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
)
}
// Alert Dialog (for confirmations)
import {
AlertDialog,
AlertDialogBody,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogContent,
AlertDialogOverlay,
} from '@chakra-ui/react'
function AlertDialogDemo() {
const { isOpen, onOpen, onClose } = useDisclosure()
const cancelRef = useRef<HTMLButtonElement>(null)
return (
<>
<Button colorScheme="red" onClick={onOpen}>
Delete Item
</Button>
<AlertDialog
isOpen={isOpen}
leastDestructiveRef={cancelRef}
onClose={onClose}
isCentered
>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader>Delete Item</AlertDialogHeader>
<AlertDialogBody>
Are you sure? You can't undo this action afterwards.
</AlertDialogBody>
<AlertDialogFooter>
<Button ref={cancelRef} onClick={onClose}>
Cancel
</Button>
<Button colorScheme="red" onClick={onClose} ml={3}>
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</>
)
}
// Drawer (side panel)
import {
Drawer,
DrawerBody,
DrawerFooter,
DrawerHeader,
DrawerOverlay,
DrawerContent,
DrawerCloseButton,
} from '@chakra-ui/react'
function DrawerDemo() {
const { isOpen, onOpen, onClose } = useDisclosure()
return (
<>
<Button onClick={onOpen}>Open Menu</Button>
<Drawer isOpen={isOpen} placement="left" onClose={onClose}>
<DrawerOverlay />
<DrawerContent>
<DrawerCloseButton />
<DrawerHeader>Navigation</DrawerHeader>
<DrawerBody>
<VStack align="stretch" spacing={4}>
<Button variant="ghost" justifyContent="flex-start">Home</Button>
<Button variant="ghost" justifyContent="flex-start">About</Button>
<Button variant="ghost" justifyContent="flex-start">Contact</Button>
</VStack>
</DrawerBody>
<DrawerFooter>
<Button variant="outline" mr={3} onClick={onClose}>
Close
</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
</>
)
}Toast
import { Button, useToast, Stack } from '@chakra-ui/react'
function ToastDemo() {
const toast = useToast()
return (
<Stack spacing={4}>
{/* Basic toast */}
<Button
onClick={() =>
toast({
title: 'Account created.',
description: "We've created your account for you.",
status: 'success',
duration: 5000,
isClosable: true,
})
}
>
Show Success Toast
</Button>
{/* Error toast */}
<Button
colorScheme="red"
onClick={() =>
toast({
title: 'Error occurred.',
description: 'Unable to create your account.',
status: 'error',
duration: 5000,
isClosable: true,
})
}
>
Show Error Toast
</Button>
{/* Warning toast */}
<Button
colorScheme="orange"
onClick={() =>
toast({
title: 'Warning',
description: 'Your session will expire soon.',
status: 'warning',
duration: 5000,
isClosable: true,
})
}
>
Show Warning Toast
</Button>
{/* Info toast */}
<Button
colorScheme="blue"
onClick={() =>
toast({
title: 'Info',
description: 'Chakra UI is awesome!',
status: 'info',
duration: 5000,
isClosable: true,
})
}
>
Show Info Toast
</Button>
{/* Position */}
<Button
onClick={() =>
toast({
title: 'Top right toast',
status: 'info',
position: 'top-right',
})
}
>
Top Right Toast
</Button>
{/* Promise toast */}
<Button
onClick={() => {
const promise = new Promise((resolve) => {
setTimeout(() => resolve('Data loaded'), 2000)
})
toast.promise(promise, {
success: { title: 'Success', description: 'Data loaded' },
error: { title: 'Error', description: 'Something went wrong' },
loading: { title: 'Loading', description: 'Please wait...' },
})
}}
>
Promise Toast
</Button>
{/* Closeable all */}
<Button onClick={() => toast.closeAll()}>
Close All Toasts
</Button>
</Stack>
)
}Card
import {
Card,
CardHeader,
CardBody,
CardFooter,
Heading,
Text,
Button,
Stack,
Image,
Divider,
ButtonGroup,
} from '@chakra-ui/react'
function CardDemo() {
return (
<Stack spacing={6}>
{/* Basic card */}
<Card>
<CardBody>
<Text>View a summary of all your customers over the last month.</Text>
</CardBody>
</Card>
{/* Card with header and footer */}
<Card>
<CardHeader>
<Heading size="md">Customer Reports</Heading>
</CardHeader>
<CardBody>
<Text>
View a summary of all your customers over the last month.
</Text>
</CardBody>
<CardFooter>
<Button colorScheme="blue">View here</Button>
</CardFooter>
</Card>
{/* Card with image */}
<Card maxW="sm">
<CardBody>
<Image
src="https://images.unsplash.com/photo-1555041469-a586c61ea9bc"
alt="Green double couch"
borderRadius="lg"
/>
<Stack mt={6} spacing={3}>
<Heading size="md">Living room Sofa</Heading>
<Text>
This sofa is perfect for modern tropical spaces, baroque inspired
spaces, and everything in between.
</Text>
<Text color="blue.600" fontSize="2xl">
$450
</Text>
</Stack>
</CardBody>
<Divider />
<CardFooter>
<ButtonGroup spacing={2}>
<Button variant="solid" colorScheme="blue">
Buy now
</Button>
<Button variant="ghost" colorScheme="blue">
Add to cart
</Button>
</ButtonGroup>
</CardFooter>
</Card>
{/* Horizontal card */}
<Card
direction={{ base: 'column', sm: 'row' }}
overflow="hidden"
variant="outline"
>
<Image
objectFit="cover"
maxW={{ base: '100%', sm: '200px' }}
src="https://images.unsplash.com/photo-1667489022797-ab608913feeb"
alt="Caffe Latte"
/>
<Stack>
<CardBody>
<Heading size="md">The perfect latte</Heading>
<Text py={2}>
Caffè latte is a coffee beverage of Italian origin made with
espresso and steamed milk.
</Text>
</CardBody>
<CardFooter>
<Button variant="solid" colorScheme="blue">
Buy Latte
</Button>
</CardFooter>
</Stack>
</Card>
</Stack>
)
}Tabs
import {
Tabs,
TabList,
TabPanels,
Tab,
TabPanel,
TabIndicator,
} from '@chakra-ui/react'
function TabsDemo() {
return (
<Stack spacing={8}>
{/* Basic tabs */}
<Tabs>
<TabList>
<Tab>One</Tab>
<Tab>Two</Tab>
<Tab>Three</Tab>
</TabList>
<TabPanels>
<TabPanel>
<p>Content for tab one</p>
</TabPanel>
<TabPanel>
<p>Content for tab two</p>
</TabPanel>
<TabPanel>
<p>Content for tab three</p>
</TabPanel>
</TabPanels>
</Tabs>
{/* Colored tabs */}
<Tabs variant="soft-rounded" colorScheme="green">
<TabList>
<Tab>Tab 1</Tab>
<Tab>Tab 2</Tab>
</TabList>
<TabPanels>
<TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
</TabPanels>
</Tabs>
{/* Enclosed tabs */}
<Tabs variant="enclosed">
<TabList>
<Tab>Tab 1</Tab>
<Tab>Tab 2</Tab>
</TabList>
<TabPanels>
<TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
</TabPanels>
</Tabs>
{/* With custom indicator */}
<Tabs position="relative" variant="unstyled">
<TabList>
<Tab>One</Tab>
<Tab>Two</Tab>
<Tab>Three</Tab>
</TabList>
<TabIndicator mt="-1.5px" height="2px" bg="blue.500" borderRadius="1px" />
<TabPanels>
<TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
<TabPanel>Content 3</TabPanel>
</TabPanels>
</Tabs>
</Stack>
)
}Dark Mode
Setup
// theme.ts
import { extendTheme, type ThemeConfig } from '@chakra-ui/react'
const config: ThemeConfig = {
initialColorMode: 'light',
useSystemColorMode: false, // lub true dla automatycznego
}
const theme = extendTheme({ config })
export default themeColor Mode Toggle
import { Button, useColorMode, useColorModeValue, Box, IconButton } from '@chakra-ui/react'
import { MoonIcon, SunIcon } from '@chakra-ui/icons'
function ColorModeToggle() {
const { colorMode, toggleColorMode } = useColorMode()
return (
<Button onClick={toggleColorMode}>
Toggle {colorMode === 'light' ? 'Dark' : 'Light'}
</Button>
)
}
// Icon button version
function ColorModeIconButton() {
const { colorMode, toggleColorMode } = useColorMode()
return (
<IconButton
aria-label="Toggle color mode"
icon={colorMode === 'light' ? <MoonIcon /> : <SunIcon />}
onClick={toggleColorMode}
/>
)
}
// Using useColorModeValue for dynamic values
function DynamicColorBox() {
const bgColor = useColorModeValue('white', 'gray.800')
const textColor = useColorModeValue('gray.800', 'white')
const borderColor = useColorModeValue('gray.200', 'gray.600')
return (
<Box
bg={bgColor}
color={textColor}
borderWidth="1px"
borderColor={borderColor}
p={4}
borderRadius="md"
>
This box adapts to color mode
</Box>
)
}
// Alternative: using _light and _dark props
function AlternativeSyntax() {
return (
<Box
bg="white"
_dark={{ bg: 'gray.800' }}
color="gray.800"
_dark={{ color: 'white' }}
p={4}
>
Using _dark pseudo prop
</Box>
)
}Theming
Custom Theme
import { extendTheme } from '@chakra-ui/react'
const theme = extendTheme({
// Colors
colors: {
brand: {
50: '#f5f0ff',
100: '#ede5ff',
200: '#d4c4ff',
300: '#b69eff',
400: '#9a7aff',
500: '#805ad5', // main brand color
600: '#6b46c1',
700: '#553c9a',
800: '#44337a',
900: '#322659',
},
},
// Fonts
fonts: {
heading: `'Inter', sans-serif`,
body: `'Inter', sans-serif`,
},
// Font sizes
fontSizes: {
xs: '0.75rem',
sm: '0.875rem',
md: '1rem',
lg: '1.125rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem',
'4xl': '2.25rem',
},
// Breakpoints
breakpoints: {
sm: '30em',
md: '48em',
lg: '62em',
xl: '80em',
'2xl': '96em',
},
// Spacing
space: {
px: '1px',
0.5: '0.125rem',
1: '0.25rem',
// ... etc
},
// Border radius
radii: {
none: '0',
sm: '0.125rem',
base: '0.25rem',
md: '0.375rem',
lg: '0.5rem',
xl: '0.75rem',
'2xl': '1rem',
full: '9999px',
},
// Component styles
components: {
Button: {
// Base styles for all buttons
baseStyle: {
fontWeight: 'semibold',
borderRadius: 'md',
},
// Size variants
sizes: {
sm: {
fontSize: 'sm',
px: 4,
py: 2,
},
md: {
fontSize: 'md',
px: 6,
py: 3,
},
},
// Visual variants
variants: {
primary: {
bg: 'brand.500',
color: 'white',
_hover: { bg: 'brand.600' },
},
secondary: {
bg: 'gray.100',
color: 'gray.800',
_hover: { bg: 'gray.200' },
},
},
// Default props
defaultProps: {
size: 'md',
variant: 'primary',
},
},
Input: {
baseStyle: {
field: {
borderRadius: 'md',
},
},
variants: {
filled: {
field: {
bg: 'gray.100',
_hover: { bg: 'gray.200' },
_focus: { bg: 'white', borderColor: 'brand.500' },
},
},
},
defaultProps: {
variant: 'filled',
},
},
Card: {
baseStyle: {
container: {
borderRadius: 'lg',
boxShadow: 'md',
},
},
},
},
// Global styles
styles: {
global: {
body: {
bg: 'gray.50',
color: 'gray.800',
},
a: {
color: 'brand.500',
_hover: { textDecoration: 'underline' },
},
},
},
})
export default themeUsing Custom Theme
import { ChakraProvider } from '@chakra-ui/react'
import theme from './theme'
function App() {
return (
<ChakraProvider theme={theme}>
<Button variant="primary">Primary Button</Button>
<Button variant="secondary">Secondary Button</Button>
</ChakraProvider>
)
}Hooks
import {
useDisclosure,
useClipboard,
useMediaQuery,
useBreakpointValue,
useBoolean,
useCounter,
useOutsideClick,
useControllableState,
} from '@chakra-ui/react'
// useDisclosure - for modals, drawers, etc.
function DisclosureDemo() {
const { isOpen, onOpen, onClose, onToggle } = useDisclosure()
return (
<>
<Button onClick={onOpen}>Open</Button>
<Modal isOpen={isOpen} onClose={onClose}>...</Modal>
</>
)
}
// useClipboard
function ClipboardDemo() {
const { hasCopied, onCopy } = useClipboard('Hello, World!')
return (
<Button onClick={onCopy}>
{hasCopied ? 'Copied!' : 'Copy'}
</Button>
)
}
// useMediaQuery
function MediaQueryDemo() {
const [isLargerThan768] = useMediaQuery('(min-width: 768px)')
return (
<Text>{isLargerThan768 ? 'Desktop' : 'Mobile'}</Text>
)
}
// useBreakpointValue
function BreakpointValueDemo() {
const buttonSize = useBreakpointValue({ base: 'sm', md: 'md', lg: 'lg' })
return <Button size={buttonSize}>Responsive Button</Button>
}
// useBoolean
function BooleanDemo() {
const [flag, setFlag] = useBoolean()
return (
<>
<Text>{flag ? 'True' : 'False'}</Text>
<Button onClick={setFlag.toggle}>Toggle</Button>
<Button onClick={setFlag.on}>Set True</Button>
<Button onClick={setFlag.off}>Set False</Button>
</>
)
}
// useCounter
function CounterDemo() {
const { value, increment, decrement, reset } = useCounter({
defaultValue: 0,
min: 0,
max: 10,
})
return (
<>
<Text>{value}</Text>
<Button onClick={() => increment()}>+</Button>
<Button onClick={() => decrement()}>-</Button>
<Button onClick={reset}>Reset</Button>
</>
)
}
// useOutsideClick
function OutsideClickDemo() {
const ref = useRef<HTMLDivElement>(null)
const [isOpen, setIsOpen] = useState(false)
useOutsideClick({
ref,
handler: () => setIsOpen(false),
})
return (
<>
<Button onClick={() => setIsOpen(true)}>Open Menu</Button>
{isOpen && (
<Box ref={ref} bg="white" p={4} shadow="md">
Click outside to close
</Box>
)}
</>
)
}Integracja z formularzami
React Hook Form + Chakra UI
import { useForm } from 'react-hook-form'
import {
FormControl,
FormLabel,
FormErrorMessage,
Input,
Button,
VStack,
} from '@chakra-ui/react'
interface FormData {
email: string
password: string
}
function HookFormDemo() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<FormData>()
const onSubmit = async (data: FormData) => {
await new Promise((resolve) => setTimeout(resolve, 2000))
console.log(data)
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<VStack spacing={4} align="stretch">
<FormControl isInvalid={!!errors.email}>
<FormLabel>Email</FormLabel>
<Input
{...register('email', {
required: 'Email is required',
pattern: {
value: /^\S+@\S+$/i,
message: 'Invalid email address',
},
})}
placeholder="you@example.com"
/>
<FormErrorMessage>{errors.email?.message}</FormErrorMessage>
</FormControl>
<FormControl isInvalid={!!errors.password}>
<FormLabel>Password</FormLabel>
<Input
type="password"
{...register('password', {
required: 'Password is required',
minLength: {
value: 8,
message: 'Password must be at least 8 characters',
},
})}
placeholder="Enter password"
/>
<FormErrorMessage>{errors.password?.message}</FormErrorMessage>
</FormControl>
<Button
type="submit"
colorScheme="blue"
isLoading={isSubmitting}
loadingText="Submitting"
>
Submit
</Button>
</VStack>
</form>
)
}FAQ - Najczęściej zadawane pytania
Jak Chakra UI wypada w porównaniu do Tailwind CSS?
Chakra używa style props (props na komponentach), Tailwind używa utility classes w className. Chakra ma wbudowane komponenty z dostępnością, Tailwind wymaga ręcznej implementacji. Chakra jest lepsze dla projektów React, Tailwind dla dowolnych projektów.
Czy Chakra UI jest wolniejszy od Tailwind?
Chakra używa runtime CSS-in-JS (Emotion), co teoretycznie jest wolniejsze niż statyczny CSS Tailwinda. W praktyce różnica jest minimalna dla większości aplikacji. Jeśli wydajność jest krytyczna, rozważ Tailwind lub statyczne rozwiązania.
Jak zmniejszyć bundle size?
Chakra automatycznie tree-shake'uje nieużywane komponenty. Importuj tylko to czego używasz. Dla krytycznych projektów rozważ @chakra-ui/anatomy dla partial imports.
Czy mogę używać Chakra z Next.js App Router?
Tak! Chakra działa z App Router, ale wymaga wrapper client component dla ChakraProvider. Server Components mogą renderować Chakra komponenty, ale interaktywność wymaga client boundary.
Jak stylować komponenty zewnętrzne?
Użyj Box jako wrapper i aplikuj style props. Dla głębszej customizacji użyj chakra() factory lub forwardRef.
Podsumowanie
Chakra UI to idealna biblioteka dla zespołów ceniących:
- Dostępność - Wszystkie komponenty zgodne z WAI-ARIA
- Developer Experience - Style props zamiast CSS
- Szybki development - Gotowe komponenty z sensownymi defaults
- Dark mode - Wbudowane wsparcie z useColorMode
- TypeScript - Pełne typowanie z autocomplete
- Customizacja - Rozbudowany system tematów
Jeśli budujesz aplikację React i chcesz skupić się na funkcjonalności zamiast stylowaniu, Chakra UI jest doskonałym wyborem.
Chakra UI - a complete guide to the accessible React library
What is Chakra UI?
Chakra UI is a modular React library designed with accessibility (a11y) and developer experience in mind. It stands out with a unique approach to styling -- instead of writing CSS, you use props to define styles directly on components. This makes code more readable, faster to write, and easier to maintain.
Chakra was built with composition in mind -- every component is modular and can be easily extended or customized. All components comply with WAI-ARIA, which means your application will be accessible to all users.
Why Chakra UI?
Key advantages of Chakra UI
- Accessibility (a11y) - All components are WAI-ARIA compliant
- Style Props - Styling through props instead of CSS
- Composition - Modular components that are easy to extend
- Dark Mode - Built-in support with useColorMode
- Responsiveness - Responsive styles via array/object syntax
- TypeScript - Full support with autocomplete
- Theming - Extensive theming system
Chakra UI vs other libraries
| Feature | Chakra UI | Mantine | MUI | Tailwind |
|---|---|---|---|---|
| Approach | Style props | CSS Modules | CSS-in-JS | Utility classes |
| Accessibility | Built-in | Built-in | Built-in | Manual |
| Dark mode | useColorMode | useColorScheme | ThemeProvider | Manual |
| Bundle size | 30-100KB | 40-150KB | 100-300KB | 10-30KB |
| Customization | High | High | Medium | Full |
| Learning curve | Low | Medium | High | Medium |
Installation and setup
Installation
# npm
npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion
# yarn
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion
# pnpm
pnpm add @chakra-ui/react @emotion/react @emotion/styled framer-motionProvider setup
// app/providers.tsx (Next.js App Router)
'use client'
import { ChakraProvider, extendTheme } from '@chakra-ui/react'
const theme = extendTheme({
// Custom theme configuration
})
export function Providers({ children }: { children: React.ReactNode }) {
return <ChakraProvider theme={theme}>{children}</ChakraProvider>
}// app/layout.tsx
import { Providers } from './providers'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="pl">
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}For Pages Router (Next.js)
// pages/_app.tsx
import { ChakraProvider } from '@chakra-ui/react'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
return (
<ChakraProvider>
<Component {...pageProps} />
</ChakraProvider>
)
}Style Props - the heart of Chakra UI
Style Props basics
import { Box, Text, Flex, Stack } from '@chakra-ui/react'
function StylePropsDemo() {
return (
<>
{/* Spacing (margin, padding) */}
<Box m={4} p={6}>Margin 16px, Padding 24px</Box>
<Box mt={2} mb={4} px={8}>Top margin 8px, Bottom 16px, Horizontal padding 32px</Box>
{/* Colors */}
<Box bg="blue.500" color="white">Blue background, white text</Box>
<Box bg="gray.100" color="gray.800">Gray shades</Box>
<Box bgGradient="linear(to-r, blue.500, purple.500)">Gradient</Box>
{/* Typography */}
<Text fontSize="xl" fontWeight="bold">Extra large bold</Text>
<Text fontSize="sm" color="gray.500">Small gray text</Text>
<Text textAlign="center" textTransform="uppercase">Centered uppercase</Text>
{/* Borders */}
<Box border="1px" borderColor="gray.200" borderRadius="md">Border</Box>
<Box borderWidth="2px" borderStyle="dashed" borderColor="blue.500">Dashed border</Box>
{/* Layout */}
<Box w="100%" h="200px">Full width, 200px height</Box>
<Box maxW="container.md" mx="auto">Centered container</Box>
{/* Flexbox */}
<Flex justify="space-between" align="center" gap={4}>
<Box>Item 1</Box>
<Box>Item 2</Box>
<Box>Item 3</Box>
</Flex>
{/* Stack (simplified flex) */}
<Stack spacing={4} direction="row">
<Box>Item 1</Box>
<Box>Item 2</Box>
</Stack>
{/* Position */}
<Box position="relative">
<Box position="absolute" top={0} right={0}>Positioned</Box>
</Box>
{/* Shadow */}
<Box shadow="md">Medium shadow</Box>
<Box shadow="lg">Large shadow</Box>
<Box shadow="2xl">Extra large shadow</Box>
</>
)
}Responsive styles
import { Box, Text, Flex, SimpleGrid } from '@chakra-ui/react'
function ResponsiveDemo() {
return (
<>
{/* Array syntax (mobile-first) */}
<Box
fontSize={['sm', 'md', 'lg', 'xl']}
// sm (base), md (480px), lg (768px), xl (992px)
>
Responsive font size
</Box>
{/* Object syntax */}
<Box
fontSize={{ base: 'sm', md: 'lg', xl: '2xl' }}
p={{ base: 2, md: 4, lg: 8 }}
bg={{ base: 'blue.100', md: 'green.100', lg: 'purple.100' }}
>
Object syntax
</Box>
{/* Responsive Flex direction */}
<Flex
direction={{ base: 'column', md: 'row' }}
gap={4}
>
<Box flex={1}>Sidebar</Box>
<Box flex={3}>Main content</Box>
</Flex>
{/* SimpleGrid with responsive columns */}
<SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} spacing={4}>
<Box bg="gray.100" p={4}>Card 1</Box>
<Box bg="gray.100" p={4}>Card 2</Box>
<Box bg="gray.100" p={4}>Card 3</Box>
</SimpleGrid>
{/* Hide/Show on breakpoints */}
<Box display={{ base: 'none', md: 'block' }}>
Visible only on md and up
</Box>
<Box display={{ base: 'block', md: 'none' }}>
Visible only on mobile
</Box>
</>
)
}Pseudo styles
import { Box, Button } from '@chakra-ui/react'
function PseudoDemo() {
return (
<>
{/* Hover */}
<Box
bg="blue.500"
_hover={{ bg: 'blue.600', transform: 'scale(1.05)' }}
transition="all 0.2s"
>
Hover me
</Box>
{/* Focus */}
<Button
_focus={{ boxShadow: 'outline', outline: 'none' }}
_focusVisible={{ ring: 2, ringColor: 'blue.500' }}
>
Focus me
</Button>
{/* Active */}
<Button
_active={{ bg: 'blue.700', transform: 'scale(0.98)' }}
>
Click me
</Button>
{/* Disabled */}
<Button
isDisabled
_disabled={{ opacity: 0.5, cursor: 'not-allowed' }}
>
Disabled
</Button>
{/* Before/After */}
<Box
position="relative"
_before={{
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '4px',
bg: 'blue.500',
}}
>
Box with top border
</Box>
{/* Dark mode specific */}
<Box
bg="white"
color="gray.800"
_dark={{ bg: 'gray.800', color: 'white' }}
>
Light/dark mode aware
</Box>
</>
)
}Components
Button
import { Button, IconButton, ButtonGroup, Stack } from '@chakra-ui/react'
import { AddIcon, DeleteIcon, SettingsIcon } from '@chakra-ui/icons'
function ButtonDemo() {
return (
<Stack spacing={4}>
{/* Variants */}
<ButtonGroup spacing={2}>
<Button colorScheme="blue">Solid (default)</Button>
<Button colorScheme="blue" variant="outline">Outline</Button>
<Button colorScheme="blue" variant="ghost">Ghost</Button>
<Button colorScheme="blue" variant="link">Link</Button>
</ButtonGroup>
{/* Color schemes */}
<ButtonGroup spacing={2}>
<Button colorScheme="gray">Gray</Button>
<Button colorScheme="red">Red</Button>
<Button colorScheme="green">Green</Button>
<Button colorScheme="blue">Blue</Button>
<Button colorScheme="teal">Teal</Button>
<Button colorScheme="purple">Purple</Button>
</ButtonGroup>
{/* Sizes */}
<ButtonGroup spacing={2}>
<Button size="xs">Extra small</Button>
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
</ButtonGroup>
{/* States */}
<ButtonGroup spacing={2}>
<Button isLoading>Loading</Button>
<Button isLoading loadingText="Saving...">With text</Button>
<Button isDisabled>Disabled</Button>
</ButtonGroup>
{/* With icons */}
<ButtonGroup spacing={2}>
<Button leftIcon={<AddIcon />} colorScheme="blue">
Add item
</Button>
<Button rightIcon={<DeleteIcon />} colorScheme="red" variant="outline">
Delete
</Button>
</ButtonGroup>
{/* Icon buttons */}
<ButtonGroup spacing={2}>
<IconButton
aria-label="Add"
icon={<AddIcon />}
colorScheme="blue"
/>
<IconButton
aria-label="Settings"
icon={<SettingsIcon />}
variant="outline"
/>
<IconButton
aria-label="Delete"
icon={<DeleteIcon />}
colorScheme="red"
isRound
/>
</ButtonGroup>
</Stack>
)
}Form Controls
import {
FormControl,
FormLabel,
FormErrorMessage,
FormHelperText,
Input,
InputGroup,
InputLeftAddon,
InputRightElement,
Textarea,
Select,
Checkbox,
CheckboxGroup,
Radio,
RadioGroup,
Switch,
Slider,
SliderTrack,
SliderFilledTrack,
SliderThumb,
NumberInput,
NumberInputField,
NumberInputStepper,
NumberIncrementStepper,
NumberDecrementStepper,
PinInput,
PinInputField,
Stack,
HStack,
Button,
} from '@chakra-ui/react'
import { useState } from 'react'
import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons'
function FormDemo() {
const [showPassword, setShowPassword] = useState(false)
const [email, setEmail] = useState('')
const isError = email === ''
return (
<Stack spacing={6} maxW="md">
{/* Basic Input */}
<FormControl>
<FormLabel>Name</FormLabel>
<Input placeholder="Enter your name" />
<FormHelperText>We'll never share your name.</FormHelperText>
</FormControl>
{/* Input with error */}
<FormControl isInvalid={isError}>
<FormLabel>Email</FormLabel>
<Input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
/>
{isError ? (
<FormErrorMessage>Email is required.</FormErrorMessage>
) : (
<FormHelperText>Enter your email address.</FormHelperText>
)}
</FormControl>
{/* Password with show/hide */}
<FormControl>
<FormLabel>Password</FormLabel>
<InputGroup>
<Input
type={showPassword ? 'text' : 'password'}
placeholder="Enter password"
/>
<InputRightElement>
<Button
size="sm"
variant="ghost"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <ViewOffIcon /> : <ViewIcon />}
</Button>
</InputRightElement>
</InputGroup>
</FormControl>
{/* Input with addon */}
<FormControl>
<FormLabel>Website</FormLabel>
<InputGroup>
<InputLeftAddon>https://</InputLeftAddon>
<Input placeholder="mysite.com" />
</InputGroup>
</FormControl>
{/* Textarea */}
<FormControl>
<FormLabel>Description</FormLabel>
<Textarea placeholder="Enter description" resize="vertical" />
</FormControl>
{/* Select */}
<FormControl>
<FormLabel>Country</FormLabel>
<Select placeholder="Select country">
<option value="pl">Poland</option>
<option value="de">Germany</option>
<option value="us">United States</option>
</Select>
</FormControl>
{/* Checkbox */}
<FormControl>
<Checkbox colorScheme="blue">
I agree to terms and conditions
</Checkbox>
</FormControl>
{/* Checkbox group */}
<FormControl>
<FormLabel>Interests</FormLabel>
<CheckboxGroup colorScheme="blue" defaultValue={['react']}>
<Stack spacing={2}>
<Checkbox value="react">React</Checkbox>
<Checkbox value="vue">Vue</Checkbox>
<Checkbox value="angular">Angular</Checkbox>
</Stack>
</CheckboxGroup>
</FormControl>
{/* Radio group */}
<FormControl>
<FormLabel>Payment method</FormLabel>
<RadioGroup defaultValue="card">
<Stack direction="row" spacing={4}>
<Radio value="card">Credit Card</Radio>
<Radio value="paypal">PayPal</Radio>
<Radio value="bank">Bank Transfer</Radio>
</Stack>
</RadioGroup>
</FormControl>
{/* Switch */}
<FormControl display="flex" alignItems="center">
<FormLabel mb={0}>Enable notifications?</FormLabel>
<Switch colorScheme="blue" />
</FormControl>
{/* Slider */}
<FormControl>
<FormLabel>Volume</FormLabel>
<Slider defaultValue={30} min={0} max={100}>
<SliderTrack>
<SliderFilledTrack />
</SliderTrack>
<SliderThumb />
</Slider>
</FormControl>
{/* Number input */}
<FormControl>
<FormLabel>Quantity</FormLabel>
<NumberInput defaultValue={1} min={1} max={20}>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</FormControl>
{/* PIN input */}
<FormControl>
<FormLabel>Verification code</FormLabel>
<HStack>
<PinInput>
<PinInputField />
<PinInputField />
<PinInputField />
<PinInputField />
</PinInput>
</HStack>
</FormControl>
</Stack>
)
}Modal
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalFooter,
ModalBody,
ModalCloseButton,
useDisclosure,
Button,
FormControl,
FormLabel,
Input,
VStack,
} from '@chakra-ui/react'
import { useRef } from 'react'
function ModalDemo() {
const { isOpen, onOpen, onClose } = useDisclosure()
const initialRef = useRef<HTMLInputElement>(null)
return (
<>
<Button onClick={onOpen} colorScheme="blue">
Open Modal
</Button>
<Modal
isOpen={isOpen}
onClose={onClose}
initialFocusRef={initialRef}
isCentered
>
<ModalOverlay />
<ModalContent>
<ModalHeader>Create your account</ModalHeader>
<ModalCloseButton />
<ModalBody>
<VStack spacing={4}>
<FormControl>
<FormLabel>Name</FormLabel>
<Input ref={initialRef} placeholder="Enter your name" />
</FormControl>
<FormControl>
<FormLabel>Email</FormLabel>
<Input type="email" placeholder="you@example.com" />
</FormControl>
</VStack>
</ModalBody>
<ModalFooter>
<Button variant="ghost" mr={3} onClick={onClose}>
Cancel
</Button>
<Button colorScheme="blue" onClick={onClose}>
Create
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
)
}
// Alert Dialog (for confirmations)
import {
AlertDialog,
AlertDialogBody,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogContent,
AlertDialogOverlay,
} from '@chakra-ui/react'
function AlertDialogDemo() {
const { isOpen, onOpen, onClose } = useDisclosure()
const cancelRef = useRef<HTMLButtonElement>(null)
return (
<>
<Button colorScheme="red" onClick={onOpen}>
Delete Item
</Button>
<AlertDialog
isOpen={isOpen}
leastDestructiveRef={cancelRef}
onClose={onClose}
isCentered
>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader>Delete Item</AlertDialogHeader>
<AlertDialogBody>
Are you sure? You can't undo this action afterwards.
</AlertDialogBody>
<AlertDialogFooter>
<Button ref={cancelRef} onClick={onClose}>
Cancel
</Button>
<Button colorScheme="red" onClick={onClose} ml={3}>
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</>
)
}
// Drawer (side panel)
import {
Drawer,
DrawerBody,
DrawerFooter,
DrawerHeader,
DrawerOverlay,
DrawerContent,
DrawerCloseButton,
} from '@chakra-ui/react'
function DrawerDemo() {
const { isOpen, onOpen, onClose } = useDisclosure()
return (
<>
<Button onClick={onOpen}>Open Menu</Button>
<Drawer isOpen={isOpen} placement="left" onClose={onClose}>
<DrawerOverlay />
<DrawerContent>
<DrawerCloseButton />
<DrawerHeader>Navigation</DrawerHeader>
<DrawerBody>
<VStack align="stretch" spacing={4}>
<Button variant="ghost" justifyContent="flex-start">Home</Button>
<Button variant="ghost" justifyContent="flex-start">About</Button>
<Button variant="ghost" justifyContent="flex-start">Contact</Button>
</VStack>
</DrawerBody>
<DrawerFooter>
<Button variant="outline" mr={3} onClick={onClose}>
Close
</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
</>
)
}Toast
import { Button, useToast, Stack } from '@chakra-ui/react'
function ToastDemo() {
const toast = useToast()
return (
<Stack spacing={4}>
{/* Basic toast */}
<Button
onClick={() =>
toast({
title: 'Account created.',
description: "We've created your account for you.",
status: 'success',
duration: 5000,
isClosable: true,
})
}
>
Show Success Toast
</Button>
{/* Error toast */}
<Button
colorScheme="red"
onClick={() =>
toast({
title: 'Error occurred.',
description: 'Unable to create your account.',
status: 'error',
duration: 5000,
isClosable: true,
})
}
>
Show Error Toast
</Button>
{/* Warning toast */}
<Button
colorScheme="orange"
onClick={() =>
toast({
title: 'Warning',
description: 'Your session will expire soon.',
status: 'warning',
duration: 5000,
isClosable: true,
})
}
>
Show Warning Toast
</Button>
{/* Info toast */}
<Button
colorScheme="blue"
onClick={() =>
toast({
title: 'Info',
description: 'Chakra UI is awesome!',
status: 'info',
duration: 5000,
isClosable: true,
})
}
>
Show Info Toast
</Button>
{/* Position */}
<Button
onClick={() =>
toast({
title: 'Top right toast',
status: 'info',
position: 'top-right',
})
}
>
Top Right Toast
</Button>
{/* Promise toast */}
<Button
onClick={() => {
const promise = new Promise((resolve) => {
setTimeout(() => resolve('Data loaded'), 2000)
})
toast.promise(promise, {
success: { title: 'Success', description: 'Data loaded' },
error: { title: 'Error', description: 'Something went wrong' },
loading: { title: 'Loading', description: 'Please wait...' },
})
}}
>
Promise Toast
</Button>
{/* Closeable all */}
<Button onClick={() => toast.closeAll()}>
Close All Toasts
</Button>
</Stack>
)
}Card
import {
Card,
CardHeader,
CardBody,
CardFooter,
Heading,
Text,
Button,
Stack,
Image,
Divider,
ButtonGroup,
} from '@chakra-ui/react'
function CardDemo() {
return (
<Stack spacing={6}>
{/* Basic card */}
<Card>
<CardBody>
<Text>View a summary of all your customers over the last month.</Text>
</CardBody>
</Card>
{/* Card with header and footer */}
<Card>
<CardHeader>
<Heading size="md">Customer Reports</Heading>
</CardHeader>
<CardBody>
<Text>
View a summary of all your customers over the last month.
</Text>
</CardBody>
<CardFooter>
<Button colorScheme="blue">View here</Button>
</CardFooter>
</Card>
{/* Card with image */}
<Card maxW="sm">
<CardBody>
<Image
src="https://images.unsplash.com/photo-1555041469-a586c61ea9bc"
alt="Green double couch"
borderRadius="lg"
/>
<Stack mt={6} spacing={3}>
<Heading size="md">Living room Sofa</Heading>
<Text>
This sofa is perfect for modern tropical spaces, baroque inspired
spaces, and everything in between.
</Text>
<Text color="blue.600" fontSize="2xl">
$450
</Text>
</Stack>
</CardBody>
<Divider />
<CardFooter>
<ButtonGroup spacing={2}>
<Button variant="solid" colorScheme="blue">
Buy now
</Button>
<Button variant="ghost" colorScheme="blue">
Add to cart
</Button>
</ButtonGroup>
</CardFooter>
</Card>
{/* Horizontal card */}
<Card
direction={{ base: 'column', sm: 'row' }}
overflow="hidden"
variant="outline"
>
<Image
objectFit="cover"
maxW={{ base: '100%', sm: '200px' }}
src="https://images.unsplash.com/photo-1667489022797-ab608913feeb"
alt="Caffe Latte"
/>
<Stack>
<CardBody>
<Heading size="md">The perfect latte</Heading>
<Text py={2}>
Caffè latte is a coffee beverage of Italian origin made with
espresso and steamed milk.
</Text>
</CardBody>
<CardFooter>
<Button variant="solid" colorScheme="blue">
Buy Latte
</Button>
</CardFooter>
</Stack>
</Card>
</Stack>
)
}Tabs
import {
Tabs,
TabList,
TabPanels,
Tab,
TabPanel,
TabIndicator,
} from '@chakra-ui/react'
function TabsDemo() {
return (
<Stack spacing={8}>
{/* Basic tabs */}
<Tabs>
<TabList>
<Tab>One</Tab>
<Tab>Two</Tab>
<Tab>Three</Tab>
</TabList>
<TabPanels>
<TabPanel>
<p>Content for tab one</p>
</TabPanel>
<TabPanel>
<p>Content for tab two</p>
</TabPanel>
<TabPanel>
<p>Content for tab three</p>
</TabPanel>
</TabPanels>
</Tabs>
{/* Colored tabs */}
<Tabs variant="soft-rounded" colorScheme="green">
<TabList>
<Tab>Tab 1</Tab>
<Tab>Tab 2</Tab>
</TabList>
<TabPanels>
<TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
</TabPanels>
</Tabs>
{/* Enclosed tabs */}
<Tabs variant="enclosed">
<TabList>
<Tab>Tab 1</Tab>
<Tab>Tab 2</Tab>
</TabList>
<TabPanels>
<TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
</TabPanels>
</Tabs>
{/* With custom indicator */}
<Tabs position="relative" variant="unstyled">
<TabList>
<Tab>One</Tab>
<Tab>Two</Tab>
<Tab>Three</Tab>
</TabList>
<TabIndicator mt="-1.5px" height="2px" bg="blue.500" borderRadius="1px" />
<TabPanels>
<TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
<TabPanel>Content 3</TabPanel>
</TabPanels>
</Tabs>
</Stack>
)
}Dark mode
Setup
// theme.ts
import { extendTheme, type ThemeConfig } from '@chakra-ui/react'
const config: ThemeConfig = {
initialColorMode: 'light',
useSystemColorMode: false, // or true for automatic detection
}
const theme = extendTheme({ config })
export default themeColor Mode Toggle
import { Button, useColorMode, useColorModeValue, Box, IconButton } from '@chakra-ui/react'
import { MoonIcon, SunIcon } from '@chakra-ui/icons'
function ColorModeToggle() {
const { colorMode, toggleColorMode } = useColorMode()
return (
<Button onClick={toggleColorMode}>
Toggle {colorMode === 'light' ? 'Dark' : 'Light'}
</Button>
)
}
// Icon button version
function ColorModeIconButton() {
const { colorMode, toggleColorMode } = useColorMode()
return (
<IconButton
aria-label="Toggle color mode"
icon={colorMode === 'light' ? <MoonIcon /> : <SunIcon />}
onClick={toggleColorMode}
/>
)
}
// Using useColorModeValue for dynamic values
function DynamicColorBox() {
const bgColor = useColorModeValue('white', 'gray.800')
const textColor = useColorModeValue('gray.800', 'white')
const borderColor = useColorModeValue('gray.200', 'gray.600')
return (
<Box
bg={bgColor}
color={textColor}
borderWidth="1px"
borderColor={borderColor}
p={4}
borderRadius="md"
>
This box adapts to color mode
</Box>
)
}
// Alternative: using _light and _dark props
function AlternativeSyntax() {
return (
<Box
bg="white"
_dark={{ bg: 'gray.800' }}
color="gray.800"
_dark={{ color: 'white' }}
p={4}
>
Using _dark pseudo prop
</Box>
)
}Theming
Custom theme
import { extendTheme } from '@chakra-ui/react'
const theme = extendTheme({
// Colors
colors: {
brand: {
50: '#f5f0ff',
100: '#ede5ff',
200: '#d4c4ff',
300: '#b69eff',
400: '#9a7aff',
500: '#805ad5', // main brand color
600: '#6b46c1',
700: '#553c9a',
800: '#44337a',
900: '#322659',
},
},
// Fonts
fonts: {
heading: `'Inter', sans-serif`,
body: `'Inter', sans-serif`,
},
// Font sizes
fontSizes: {
xs: '0.75rem',
sm: '0.875rem',
md: '1rem',
lg: '1.125rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem',
'4xl': '2.25rem',
},
// Breakpoints
breakpoints: {
sm: '30em',
md: '48em',
lg: '62em',
xl: '80em',
'2xl': '96em',
},
// Spacing
space: {
px: '1px',
0.5: '0.125rem',
1: '0.25rem',
// ... etc
},
// Border radius
radii: {
none: '0',
sm: '0.125rem',
base: '0.25rem',
md: '0.375rem',
lg: '0.5rem',
xl: '0.75rem',
'2xl': '1rem',
full: '9999px',
},
// Component styles
components: {
Button: {
// Base styles for all buttons
baseStyle: {
fontWeight: 'semibold',
borderRadius: 'md',
},
// Size variants
sizes: {
sm: {
fontSize: 'sm',
px: 4,
py: 2,
},
md: {
fontSize: 'md',
px: 6,
py: 3,
},
},
// Visual variants
variants: {
primary: {
bg: 'brand.500',
color: 'white',
_hover: { bg: 'brand.600' },
},
secondary: {
bg: 'gray.100',
color: 'gray.800',
_hover: { bg: 'gray.200' },
},
},
// Default props
defaultProps: {
size: 'md',
variant: 'primary',
},
},
Input: {
baseStyle: {
field: {
borderRadius: 'md',
},
},
variants: {
filled: {
field: {
bg: 'gray.100',
_hover: { bg: 'gray.200' },
_focus: { bg: 'white', borderColor: 'brand.500' },
},
},
},
defaultProps: {
variant: 'filled',
},
},
Card: {
baseStyle: {
container: {
borderRadius: 'lg',
boxShadow: 'md',
},
},
},
},
// Global styles
styles: {
global: {
body: {
bg: 'gray.50',
color: 'gray.800',
},
a: {
color: 'brand.500',
_hover: { textDecoration: 'underline' },
},
},
},
})
export default themeUsing custom theme
import { ChakraProvider } from '@chakra-ui/react'
import theme from './theme'
function App() {
return (
<ChakraProvider theme={theme}>
<Button variant="primary">Primary Button</Button>
<Button variant="secondary">Secondary Button</Button>
</ChakraProvider>
)
}Hooks
import {
useDisclosure,
useClipboard,
useMediaQuery,
useBreakpointValue,
useBoolean,
useCounter,
useOutsideClick,
useControllableState,
} from '@chakra-ui/react'
// useDisclosure - for modals, drawers, etc.
function DisclosureDemo() {
const { isOpen, onOpen, onClose, onToggle } = useDisclosure()
return (
<>
<Button onClick={onOpen}>Open</Button>
<Modal isOpen={isOpen} onClose={onClose}>...</Modal>
</>
)
}
// useClipboard
function ClipboardDemo() {
const { hasCopied, onCopy } = useClipboard('Hello, World!')
return (
<Button onClick={onCopy}>
{hasCopied ? 'Copied!' : 'Copy'}
</Button>
)
}
// useMediaQuery
function MediaQueryDemo() {
const [isLargerThan768] = useMediaQuery('(min-width: 768px)')
return (
<Text>{isLargerThan768 ? 'Desktop' : 'Mobile'}</Text>
)
}
// useBreakpointValue
function BreakpointValueDemo() {
const buttonSize = useBreakpointValue({ base: 'sm', md: 'md', lg: 'lg' })
return <Button size={buttonSize}>Responsive Button</Button>
}
// useBoolean
function BooleanDemo() {
const [flag, setFlag] = useBoolean()
return (
<>
<Text>{flag ? 'True' : 'False'}</Text>
<Button onClick={setFlag.toggle}>Toggle</Button>
<Button onClick={setFlag.on}>Set True</Button>
<Button onClick={setFlag.off}>Set False</Button>
</>
)
}
// useCounter
function CounterDemo() {
const { value, increment, decrement, reset } = useCounter({
defaultValue: 0,
min: 0,
max: 10,
})
return (
<>
<Text>{value}</Text>
<Button onClick={() => increment()}>+</Button>
<Button onClick={() => decrement()}>-</Button>
<Button onClick={reset}>Reset</Button>
</>
)
}
// useOutsideClick
function OutsideClickDemo() {
const ref = useRef<HTMLDivElement>(null)
const [isOpen, setIsOpen] = useState(false)
useOutsideClick({
ref,
handler: () => setIsOpen(false),
})
return (
<>
<Button onClick={() => setIsOpen(true)}>Open Menu</Button>
{isOpen && (
<Box ref={ref} bg="white" p={4} shadow="md">
Click outside to close
</Box>
)}
</>
)
}Form integration
React Hook Form + Chakra UI
import { useForm } from 'react-hook-form'
import {
FormControl,
FormLabel,
FormErrorMessage,
Input,
Button,
VStack,
} from '@chakra-ui/react'
interface FormData {
email: string
password: string
}
function HookFormDemo() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<FormData>()
const onSubmit = async (data: FormData) => {
await new Promise((resolve) => setTimeout(resolve, 2000))
console.log(data)
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<VStack spacing={4} align="stretch">
<FormControl isInvalid={!!errors.email}>
<FormLabel>Email</FormLabel>
<Input
{...register('email', {
required: 'Email is required',
pattern: {
value: /^\S+@\S+$/i,
message: 'Invalid email address',
},
})}
placeholder="you@example.com"
/>
<FormErrorMessage>{errors.email?.message}</FormErrorMessage>
</FormControl>
<FormControl isInvalid={!!errors.password}>
<FormLabel>Password</FormLabel>
<Input
type="password"
{...register('password', {
required: 'Password is required',
minLength: {
value: 8,
message: 'Password must be at least 8 characters',
},
})}
placeholder="Enter password"
/>
<FormErrorMessage>{errors.password?.message}</FormErrorMessage>
</FormControl>
<Button
type="submit"
colorScheme="blue"
isLoading={isSubmitting}
loadingText="Submitting"
>
Submit
</Button>
</VStack>
</form>
)
}FAQ - frequently asked questions
How does Chakra UI compare to Tailwind CSS?
Chakra uses style props (props on components), while Tailwind uses utility classes in className. Chakra ships with built-in components that include accessibility out of the box, whereas Tailwind requires manual implementation. Chakra is a better fit for React-specific projects, while Tailwind works well for any kind of project.
Is Chakra UI slower than Tailwind?
Chakra uses runtime CSS-in-JS (Emotion), which is theoretically slower than Tailwind's static CSS. In practice, the difference is negligible for most applications. If performance is absolutely critical, consider Tailwind or other static solutions.
How can I reduce bundle size?
Chakra automatically tree-shakes unused components. Import only what you actually use. For performance-critical projects, consider @chakra-ui/anatomy for partial imports.
Can I use Chakra with the Next.js App Router?
Yes! Chakra works with the App Router, but it requires a wrapper client component for ChakraProvider. Server Components can render Chakra components, but interactivity requires a client boundary.
How do I style third-party components?
Use Box as a wrapper and apply style props to it. For deeper customization, use the chakra() factory function or forwardRef.
Summary
Chakra UI is the ideal library for teams that value:
- Accessibility - All components are WAI-ARIA compliant
- Developer Experience - Style props instead of CSS
- Fast development - Ready-made components with sensible defaults
- Dark mode - Built-in support with useColorMode
- TypeScript - Full type support with autocomplete
- Customization - Extensive theming system
If you are building a React application and want to focus on functionality rather than styling, Chakra UI is an excellent choice.