Website Markup Using Chakra UI
Chakra UI—a React component library focused on accessibility-first approach and style props API. Each component is a styled div with Emotion under the hood. Strengths: responsive props directly in JSX, built-in dark theme, strict WCAG compliance out of the box.
Installation (Chakra v3)
In version 3.x the architecture changed—simplified provider, removed ChakraBaseProvider dependency:
npm install @chakra-ui/react @emotion/react
// src/main.tsx
import { ChakraProvider, defaultSystem } from '@chakra-ui/react';
import system from './theme/system';
function Root() {
return (
<ChakraProvider value={system}>
<App />
</ChakraProvider>
);
}
// src/theme/system.ts
import { createSystem, defaultConfig, defineConfig } from '@chakra-ui/react';
const customConfig = defineConfig({
theme: {
tokens: {
fonts: {
heading: { value: '"Inter", sans-serif' },
body: { value: '"Inter", sans-serif' },
mono: { value: '"JetBrains Mono", monospace' },
},
colors: {
brand: {
50: { value: '#eff6ff' },
100: { value: '#dbeafe' },
500: { value: '#3b82f6' },
600: { value: '#2563eb' },
700: { value: '#1d4ed8' },
800: { value: '#1e40af' },
},
},
radii: {
card: { value: '12px' },
button: { value: '8px' },
},
},
semanticTokens: {
colors: {
'bg.canvas': {
value: { base: '#f9fafb', _dark: '#0f172a' },
},
'bg.surface': {
value: { base: '#ffffff', _dark: '#1e293b' },
},
'text.primary': {
value: { base: '#111827', _dark: '#f1f5f9' },
},
'text.muted': {
value: { base: '#6b7280', _dark: '#94a3b8' },
},
'border.default': {
value: { base: '#e5e7eb', _dark: '#334155' },
},
},
},
},
});
const system = createSystem(defaultConfig, customConfig);
export default system;
Style props API—foundation of markup
Chakra UI passes CSS properties directly through props with responsive objects support:
import { Box, Flex, Grid, Text, Heading, Button, Stack } from '@chakra-ui/react';
const HeroSection = () => (
<Box
as="section"
bg="bg.canvas"
py={{ base: '12', md: '20', lg: '28' }}
px={{ base: '4', md: '8', lg: '12' }}
>
<Flex
maxW="7xl"
mx="auto"
direction={{ base: 'column', lg: 'row' }}
align="center"
gap={{ base: '10', lg: '16' }}
>
<Box flex="1">
<Heading
as="h1"
size={{ base: '2xl', md: '3xl', lg: '4xl' }}
color="text.primary"
mb="4"
fontWeight="700"
lineHeight="1.2"
>
Your Product Headline
</Heading>
<Text
fontSize={{ base: 'md', md: 'lg' }}
color="text.muted"
mb="8"
maxW="480px"
>
Subheading describing key value for potential customers.
</Text>
<Stack direction={{ base: 'column', sm: 'row' }} gap="3">
<Button
colorPalette="brand"
size="lg"
borderRadius="button"
px="8"
>
Start Free
</Button>
<Button
variant="outline"
size="lg"
borderRadius="button"
px="8"
>
View Demo
</Button>
</Stack>
</Box>
<Box flex="1">
<Box
as="img"
src="/img/hero.webp"
alt="Product screenshot"
w="100%"
borderRadius="card"
shadow="2xl"
/>
</Box>
</Flex>
</Box>
);
Card grid
import { SimpleGrid, Card, Icon } from '@chakra-ui/react';
import { LuCode, LuCloud, LuShield } from 'react-icons/lu';
const features = [
{ icon: LuCode, title: 'Development', text: '...' },
{ icon: LuCloud, title: 'Cloud', text: '...' },
{ icon: LuShield, title: 'Security', text: '...' },
];
const FeaturesGrid = () => (
<SimpleGrid columns={{ base: 1, sm: 2, lg: 3 }} gap="6">
{features.map(({ icon, title, text }) => (
<Card.Root
key={title}
bg="bg.surface"
border="1px solid"
borderColor="border.default"
borderRadius="card"
_hover={{ shadow: 'md', transform: 'translateY(-2px)' }}
transition="all 200ms ease"
>
<Card.Body p="6">
<Box
w="10"
h="10"
bg="brand.50"
borderRadius="lg"
display="flex"
alignItems="center"
justifyContent="center"
mb="4"
>
<Icon as={icon} boxSize="5" color="brand.600" />
</Box>
<Heading as="h3" size="md" mb="2">
{title}
</Heading>
<Text color="text.muted" fontSize="sm">
{text}
</Text>
</Card.Body>
</Card.Root>
))}
</SimpleGrid>
);
Form with validation (React Hook Form + Zod)
import { Field, Input, Textarea, Button } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(2, 'Minimum 2 characters'),
email: z.string().email('Invalid email'),
message: z.string().min(10, 'Minimum 10 characters'),
});
type FormData = z.infer<typeof schema>;
const ContactForm = () => {
const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<FormData>({
resolver: zodResolver(schema),
});
return (
<Box as="form" onSubmit={handleSubmit(onSubmit)}>
<Stack gap="5">
<Field.Root invalid={!!errors.name}>
<Field.Label>Name</Field.Label>
<Input {...register('name')} placeholder="John Smith" />
{errors.name && <Field.ErrorText>{errors.name.message}</Field.ErrorText>}
</Field.Root>
<Field.Root invalid={!!errors.email}>
<Field.Label>Email</Field.Label>
<Input {...register('email')} type="email" placeholder="[email protected]" />
{errors.email && <Field.ErrorText>{errors.email.message}</Field.ErrorText>}
</Field.Root>
<Field.Root invalid={!!errors.message}>
<Field.Label>Message</Field.Label>
<Textarea {...register('message')} rows={4} />
{errors.message && <Field.ErrorText>{errors.message.message}</Field.ErrorText>}
</Field.Root>
<Button type="submit" colorPalette="brand" loading={isSubmitting} w="full">
Send
</Button>
</Stack>
</Box>
);
};
Color mode
Chakra v3 manages dark mode via semantic tokens—_dark variant is set when defining the token:
import { useColorMode } from '@chakra-ui/react';
const ThemeToggle = () => {
const { colorMode, toggleColorMode } = useColorMode();
return (
<Button onClick={toggleColorMode} variant="ghost" size="sm">
{colorMode === 'light' ? 'Dark theme' : 'Light theme'}
</Button>
);
};
Timeline
System and theme setup: 3–4 hours. Chakra UI is especially efficient for developing sites with forms, dashboards and SaaS interfaces—responsive props in JSX eliminate file switching. Landing page: 1–2 days. Full product site with multiple sections: 4–7 days.







