Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds
Torna alle collezioni
Guide34 min read

Chakra UI - Kompletny Przewodnik po Dostępnej Bibliotece React

Chakra UI is a modular and accessible React library with simple API, great DX, and full dark mode support. Discover all components, theming, and Next.js integration.

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

  1. Dostępność (a11y) - Wszystkie komponenty są zgodne z WAI-ARIA
  2. Style Props - Stylowanie przez props zamiast CSS
  3. Kompozycja - Modularne komponenty łatwe do rozszerzenia
  4. Dark Mode - Wbudowane wsparcie z useColorMode
  5. Responsywność - Responsywne style przez array/object syntax
  6. TypeScript - Pełne wsparcie z autocomplete
  7. Theming - Rozbudowany system tematów

Chakra UI vs inne biblioteki

CechaChakra UIMantineMUITailwind
PodejścieStyle propsCSS ModulesCSS-in-JSUtility classes
DostępnośćWbudowanaWbudowanaWbudowanaRęczna
Dark modeuseColorModeuseColorSchemeThemeProviderRęczne
Bundle size30-100KB40-150KB100-300KB10-30KB
CustomizacjaWysokaWysokaŚredniaPełna
Learning curveNiskaŚredniaWysokaŚrednia

Instalacja i konfiguracja

Instalacja

Code
Bash
# 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-motion

Provider setup

TSapp/providers.tsx
TypeScript
// 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>
}
TSapp/layout.tsx
TypeScript
// 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)

TSpages/_app.tsx
TypeScript
// 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

Code
TypeScript
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

Code
TypeScript
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

Code
TypeScript
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

Code
TypeScript
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

Code
TypeScript
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

Code
TypeScript
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

Code
TypeScript
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

Code
TypeScript
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

Code
TypeScript
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

TStheme.ts
TypeScript
// 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 theme

Color Mode Toggle

Code
TypeScript
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

Code
TypeScript
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 theme

Using Custom Theme

Code
TypeScript
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

Code
TypeScript
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

Code
TypeScript
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

  1. Accessibility (a11y) - All components are WAI-ARIA compliant
  2. Style Props - Styling through props instead of CSS
  3. Composition - Modular components that are easy to extend
  4. Dark Mode - Built-in support with useColorMode
  5. Responsiveness - Responsive styles via array/object syntax
  6. TypeScript - Full support with autocomplete
  7. Theming - Extensive theming system

Chakra UI vs other libraries

FeatureChakra UIMantineMUITailwind
ApproachStyle propsCSS ModulesCSS-in-JSUtility classes
AccessibilityBuilt-inBuilt-inBuilt-inManual
Dark modeuseColorModeuseColorSchemeThemeProviderManual
Bundle size30-100KB40-150KB100-300KB10-30KB
CustomizationHighHighMediumFull
Learning curveLowMediumHighMedium

Installation and setup

Installation

Code
Bash
# 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-motion

Provider setup

TSapp/providers.tsx
TypeScript
// 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>
}
TSapp/layout.tsx
TypeScript
// 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)

TSpages/_app.tsx
TypeScript
// 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

Code
TypeScript
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

Code
TypeScript
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

Code
TypeScript
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

Code
TypeScript
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

Code
TypeScript
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

Code
TypeScript
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

Code
TypeScript
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

Code
TypeScript
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

Code
TypeScript
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

TStheme.ts
TypeScript
// 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 theme

Color Mode Toggle

Code
TypeScript
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

Code
TypeScript
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 theme

Using custom theme

Code
TypeScript
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

Code
TypeScript
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

Code
TypeScript
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.