<h2>O Que é um Design System e Por Que Usá-lo</h2>
<p>Um Design System é um conjunto de componentes, tokens e diretrizes que garantem consistência visual e funcional em toda uma aplicação. Ao invés de cada desenvolvedor criar seu próprio estilo de botão, input ou card, você centraliza essas decisões em uma fonte única de verdade. Isso não é apenas sobre padronização estética — é sobre escalabilidade, manutenção e experiência do usuário.</p>
<p>No contexto de React, um Design System bem construído reduz drasticamente o tempo de desenvolvimento, diminui bugs relacionados a acessibilidade e facilita mudanças globais de branding. Quando você precisa alterar a cor primária do projeto inteiro, basta atualizar um token em um único lugar, e a mudança propaga-se automaticamente por todos os componentes que o utilizam.</p>
<h3>Por Que React é Ideal para Design Systems</h3>
<p>React oferece abstrações perfeitas para construir sistemas reutilizáveis através de componentes. Cada botão, ícone ou card torna-se um bloco funcional isolado que pode receber props para se adaptar a diferentes contextos. A composição de componentes permite criar interfaces complexas a partir de peças simples, mantendo o código limpo e testável. Além disso, o ecossistema React tem ferramentas maduras como Storybook que facilitam a documentação e o desenvolvimento de componentes em isolamento.</p>
<h2>Design Tokens: A Fundação do Sistema</h2>
<p>Design Tokens são valores reutilizáveis que definem os aspectos visuais de um design: cores, tipografia, espaçamento, sombras e raios de borda. Ao invés de escrever <code>color: #3B82F6</code> diretamente no código, você define um token chamado <code>primaryColor</code> que conterá esse valor. Isso cria uma camada de abstração que facilita manutenção e garantir consistência.</p>
<p>Os tokens devem ser organizados hierarquicamente, começando pelos mais primitivos (cores base, tamanhos) até os compostos (espaçamento de um formulário, sombra de um card). Uma boa estrutura de tokens permite que designers e desenvolvedores falem a mesma linguagem.</p>
<h3>Estrutura e Implementação de Tokens</h3>
<p>Vamos criar uma estrutura de tokens bem organizada usando JavaScript puro, que pode ser facilmente integrada com CSS-in-JS ou Tailwind:</p>
<pre><code class="language-javascript">// tokens.js
const tokens = {
colors: {
// Cores primitivas
neutral: {
50: '#F9FAFB',
100: '#F3F4F6',
200: '#E5E7EB',
300: '#D1D5DB',
400: '#9CA3AF',
500: '#6B7280',
600: '#4B5563',
700: '#374151',
800: '#1F2937',
900: '#111827',
},
primary: {
50: '#EFF6FF',
100: '#DBEAFE',
500: '#3B82F6',
600: '#2563EB',
700: '#1D4ED8',
900: '#1E3A8A',
},
semantic: {
success: '#10B981',
warning: '#F59E0B',
error: '#EF4444',
info: '#0EA5E9',
},
},
typography: {
fontFamily: {
sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
mono: 'Menlo, Monaco, Courier New, monospace',
},
fontSize: {
xs: '0.75rem', // 12px
sm: '0.875rem', // 14px
base: '1rem', // 16px
lg: '1.125rem', // 18px
xl: '1.25rem', // 20px
'2xl': '1.5rem', // 24px
},
fontWeight: {
light: 300,
normal: 400,
medium: 500,
semibold: 600,
bold: 700,
},
lineHeight: {
tight: 1.2,
normal: 1.5,
relaxed: 1.75,
},
},
spacing: {
xs: '0.25rem', // 4px
sm: '0.5rem', // 8px
md: '1rem', // 16px
lg: '1.5rem', // 24px
xl: '2rem', // 32px
'2xl': '3rem', // 48px
'3xl': '4rem', // 64px
},
borderRadius: {
none: '0',
sm: '0.25rem',
md: '0.375rem',
lg: '0.5rem',
xl: '0.75rem',
full: '9999px',
},
shadows: {
none: 'none',
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1)',
},
transitions: {
fast: '150ms ease-in-out',
base: '200ms ease-in-out',
slow: '300ms ease-in-out',
},
};
export default tokens;</code></pre>
<p>Agora criamos um contexto React para distribuir esses tokens por toda a aplicação:</p>
<pre><code class="language-javascript">// ThemeProvider.jsx
import React, { createContext, useContext } from 'react';
import tokens from './tokens';
const ThemeContext = createContext(null);
export const ThemeProvider = ({ children }) => {
return (
<ThemeContext.Provider value={tokens}>
{children}
</ThemeContext.Provider>
);
};
export const useTokens = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTokens deve ser usado dentro de ThemeProvider');
}
return context;
};</code></pre>
<h3>Usando Tokens em Componentes</h3>
<p>Com os tokens definidos, podemos criar componentes que os consomem de forma limpa:</p>
<pre><code class="language-javascript">// Button.jsx
import styled from 'styled-components';
import { useTokens } from './ThemeProvider';
const StyledButton = styled.button`
padding: ${props => props.tokens.spacing.md} ${props => props.tokens.spacing.lg};
font-size: ${props => props.tokens.typography.fontSize.base};
font-weight: ${props => props.tokens.typography.fontWeight.semibold};
border: none;
border-radius: ${props => props.tokens.borderRadius.md};
cursor: pointer;
transition: all ${props => props.tokens.transitions.base};
background-color: ${props => props.variant === 'primary'
? props.tokens.colors.primary[600]
: props.tokens.colors.neutral[100]};
color: ${props => props.variant === 'primary'
? '#FFFFFF'
: props.tokens.colors.neutral[900]};
&:hover {
background-color: ${props => props.variant === 'primary'
? props.tokens.colors.primary[700]
: props.tokens.colors.neutral[200]};
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
`;
const Button = ({ children, variant = 'primary', ...props }) => {
const tokens = useTokens();
return (
<StyledButton tokens={tokens} variant={variant} {...props}>
{children}
</StyledButton>
);
};
export default Button;</code></pre>
<h2>Variantes e Composição de Componentes</h2>
<p>Variantes são diferentes estados visuais de um componente que herdam a maioria das características base, mas diferem em aspecto específicos. Um botão pode ter variantes de tamanho (small, medium, large), tipo (primary, secondary, danger) e estado (normal, loading, disabled). A gestão inteligente de variantes evita duplicação de código e facilita a evolução consistente do componente.</p>
<h3>Implementando Variantes com CVA (Class Variance Authority)</h3>
<p>Embora o CVA seja originalmente para Tailwind, seus princípios funcionam perfeitamente em qualquer arquitetura. Vamos criar um sistema de variantes usando uma abordagem funcional pura:</p>
<pre><code class="language-javascript">// variants.js
export const createVariants = (baseStyles, variantMap) => {
return (variantConfig = {}) => {
let styles = { ...baseStyles };
Object.entries(variantConfig).forEach(([key, value]) => {
if (variantMap[key] && variantMap[key][value]) {
styles = { ...styles, ...variantMap[key][value] };
}
});
return styles;
};
};</code></pre>
<p>Agora criamos um componente Button com múltiplas variantes:</p>
<pre><code class="language-javascript">// ButtonWithVariants.jsx
import styled from 'styled-components';
import { useTokens } from './ThemeProvider';
import { createVariants } from './variants';
const ButtonBase = styled.button`
font-family: ${props => props.tokens.typography.fontFamily.sans};
border: none;
cursor: pointer;
transition: all ${props => props.tokens.transitions.base};
font-weight: ${props => props.tokens.typography.fontWeight.semibold};
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
`;
const Button = ({
children,
variant = 'primary',
size = 'md',
loading = false,
...props
}) => {
const tokens = useTokens();
const sizeVariants = {
sm: {
padding: ${tokens.spacing.sm} ${tokens.spacing.md},
fontSize: tokens.typography.fontSize.sm,
borderRadius: tokens.borderRadius.sm,
},
md: {
padding: ${tokens.spacing.md} ${tokens.spacing.lg},
fontSize: tokens.typography.fontSize.base,
borderRadius: tokens.borderRadius.md,
},
lg: {
padding: ${tokens.spacing.lg} ${tokens.spacing.xl},
fontSize: tokens.typography.fontSize.lg,
borderRadius: tokens.borderRadius.lg,
},
};
const colorVariants = {
primary: {
backgroundColor: tokens.colors.primary[600],
color: '#FFFFFF',
'&:hover': {
backgroundColor: tokens.colors.primary[700],
},
},
secondary: {
backgroundColor: tokens.colors.neutral[200],
color: tokens.colors.neutral[900],
'&:hover': {
backgroundColor: tokens.colors.neutral[300],
},
},
danger: {
backgroundColor: tokens.colors.semantic.error,
color: '#FFFFFF',
'&:hover': {
backgroundColor: '#DC2626',
},
},
};
const getStyles = createVariants(
{ ...sizeVariants[size], ...colorVariants[variant] },
{}
);
const styles = getStyles();
const StyledButton = styled(ButtonBase)`
padding: ${styles.padding};
font-size: ${styles.fontSize};
border-radius: ${styles.borderRadius};
background-color: ${styles.backgroundColor};
color: ${styles.color};
&:hover:not(:disabled) {
background-color: ${styles['&:hover'].backgroundColor};
}
`;
return (
<StyledButton
tokens={tokens}
disabled={loading || props.disabled}
{...props}
>
{loading ? '⏳ Carregando...' : children}
</StyledButton>
);
};
export default Button;</code></pre>
<h3>Composição de Componentes Complexos</h3>
<p>Componentes complexos são construídos combinando componentes simples. Vamos criar um Card que usa Button:</p>
<pre><code class="language-javascript">// Card.jsx
import styled from 'styled-components';
import { useTokens } from './ThemeProvider';
const CardContainer = styled.div`
background-color: #FFFFFF;
border-radius: ${props => props.tokens.borderRadius.lg};
box-shadow: ${props => props.tokens.shadows.md};
overflow: hidden;
`;
const CardHeader = styled.div`
padding: ${props => props.tokens.spacing.lg};
border-bottom: 1px solid ${props => props.tokens.colors.neutral[200]};
`;
const CardBody = styled.div`
padding: ${props => props.tokens.spacing.lg};
`;
const CardFooter = styled.div`
padding: ${props => props.tokens.spacing.lg};
background-color: ${props => props.tokens.colors.neutral[50]};
border-top: 1px solid ${props => props.tokens.colors.neutral[200]};
display: flex;
gap: ${props => props.tokens.spacing.md};
justify-content: flex-end;
`;
const Card = ({ title, children, onSubmit, onCancel }) => {
const tokens = useTokens();
return (
<CardContainer tokens={tokens}>
{title && (
<CardHeader tokens={tokens}>
<h2 style={{ margin: 0, fontSize: tokens.typography.fontSize.xl }}>
{title}
</h2>
</CardHeader>
)}
<CardBody tokens={tokens}>
{children}
</CardBody>
{(onSubmit || onCancel) && (
<CardFooter tokens={tokens}>
{onCancel && (
<Button variant="secondary" onClick={onCancel}>
Cancelar
</Button>
)}
{onSubmit && (
<Button variant="primary" onClick={onSubmit}>
Confirmar
</Button>
)}
</CardFooter>
)}
</CardContainer>
);
};
export default Card;</code></pre>
<h2>Acessibilidade: Tornando o Design System Inclusivo</h2>
<p>Acessibilidade não é um "extra" — é um requisito fundamental. Um design system inacessível exclui milhões de usuários e pode violar leis de conformidade como WCAG 2.1. Todos os componentes devem ser testados com leitores de tela, navegação por teclado e ferramentas de contraste de cor desde o início do desenvolvimento.</p>
<h3>Princípios WCAG 2.1 em Componentes React</h3>
<p>Os quatro princípios WCAG são: <strong>Perceptível</strong>, <strong>Operável</strong>, <strong>Compreensível</strong> e <strong>Robusto</strong>. Vamos aplicá-los a um componente Input:</p>
<pre><code class="language-javascript">// Input.jsx
import styled from 'styled-components';
import { useTokens } from './ThemeProvider';
import { useState } from 'react';
const InputWrapper = styled.div`
display: flex;
flex-direction: column;
gap: ${props => props.tokens.spacing.sm};
`;
const Label = styled.label`
font-size: ${props => props.tokens.typography.fontSize.sm};
font-weight: ${props => props.tokens.typography.fontWeight.semibold};
color: ${props => props.tokens.colors.neutral[700]};
`;
const InputField = styled.input`
padding: ${props => props.tokens.spacing.sm} ${props => props.tokens.spacing.md};
font-size: ${props => props.tokens.typography.fontSize.base};
border: 2px solid ${props => props.tokens.colors.neutral[300]};
border-radius: ${props => props.tokens.borderRadius.md};
font-family: ${props => props.tokens.typography.fontFamily.sans};
transition: all ${props => props.tokens.transitions.base};
&:focus {
outline: none;
border-color: ${props => props.tokens.colors.primary[600]};
box-shadow: 0 0 0 3px ${props => props.tokens.colors.primary[50]};
}
&:disabled {
background-color: ${props => props.tokens.colors.neutral[100]};
cursor: not-allowed;
opacity: 0.6;
}
&[aria-invalid="true"] {
border-color: ${props => props.tokens.colors.semantic.error};
}
`;
const ErrorMessage = styled.span`
font-size: ${props => props.tokens.typography.fontSize.sm};
color: ${props => props.tokens.colors.semantic.error};
display: flex;
align-items: center;
gap: ${props => props.tokens.spacing.xs};
`;
const HelperText = styled.span`
font-size: ${props => props.tokens.typography.fontSize.xs};
color: ${props => props.tokens.colors.neutral[500]};
`;
const Input = ({
id,
label,
type = 'text',
placeholder,
value,
onChange,
onBlur,
error,
helperText,
required = false,
disabled = false,
...props
}) => {
const tokens = useTokens();
const [isFocused, setIsFocused] = useState(false);
const inputId = id || input-${Math.random().toString(36).slice(2, 9)};
const errorId = ${inputId}-error;
const helperId = ${inputId}-helper;
const handleBlur = (e) => {
setIsFocused(false);
onBlur?.(e);
};
const handleFocus = () => {
setIsFocused(true);
};
const ariaDescribedBy = [
error ? errorId : null,
helperText ? helperId : null,
].filter(Boolean).join(' ');
return (
<InputWrapper tokens={tokens}>
{label && (
<Label htmlFor={inputId}>
{label}
{required && (
<span
aria-label="obrigatório"
style={{ color: tokens.colors.semantic.error }}
>
*
</span>
)}
</Label>
)}
<InputField
id={inputId}
type={type}
placeholder={placeholder}
value={value}
onChange={onChange}
onBlur={handleBlur}
onFocus={handleFocus}
disabled={disabled}
required={required}
aria-invalid={!!error}
aria-describedby={ariaDescribedBy || undefined}
tokens={tokens}
{...props}
/>
{error && (
<ErrorMessage id={errorId} role="alert" tokens={tokens}>
⚠️ {error}
</ErrorMessage>
)}
{helperText && !error && (
<HelperText id={helperId} tokens={tokens}>
{helperText}
</HelperText>
)}
</InputWrapper>
);
};
export default Input;</code></pre>
<h3>Testando Acessibilidade</h3>
<p>Para garantir que seu design system é realmente acessível, teste com ferramentas automatizadas e testes manuais:</p>
<pre><code class="language-javascript">// Button.test.jsx - usando Jest e Testing Library
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Button from './Button';
import { ThemeProvider } from './ThemeProvider';
describe('Button Accessibility', () => {
const renderButton = (props) =>
render(
<ThemeProvider>
<Button {...props}>Click me</Button>
</ThemeProvider>
);
test('deve ser focável via teclado', () => {
renderButton();
const button = screen.getByRole('button');
button.focus();
expect(button).toHaveFocus();
});
test('deve ser clicável com Enter e Space', async () => {
const user = userEvent.setup();
const handleClick = jest.fn();
renderButton({ onClick: handleClick });
const button = screen.getByRole('button');
button.focus();
await user.keyboard('{Enter}');
expect(handleClick).toHaveBeenCalled();
});
test('deve ter contraste de cor suficiente', () => {
renderButton({ variant: 'primary' });
const button = screen.getByRole('button');
const styles = window.getComputedStyle(button);
// Verificar que há uma cor de fundo e texto definidas
expect(styles.backgroundColor).toBeTruthy();
expect(styles.color).toBeTruthy();
});
test('deve desabilitar corretamente com aria-disabled', () => {
renderButton({ disabled: true });
const button = screen.getByRole('button', { hidden: true });
expect(button).toBeDisabled();
});
test('deve ter texto descritivo adequado', () => {
renderButton({ children: 'Enviar Formulário' });
expect(screen.getByRole('button')).toHaveAccessibleName(
'Enviar Formulário'
);
});
});</code></pre>
<h2>Integrando Tudo: Um Design System Completo em Ação</h2>
<p>Agora vamos demonstrar como integrar todos esses conceitos em uma aplicação real:</p>
<pre><code class="language-javascript"></code></pre>
<h2>Conclusão</h2>
<p>Você aprendeu que um <strong>Design System sólido é construído em três pilares</strong>: tokens bem estruturados que centralizam decisões visuais, variantes de componentes que oferecem flexibilidade sem sacrificar consistência, e acessibilidade integrada desde o início, não como "bônus". O React oferece as abstrações perfeitas para implementar esses conceitos através de Context API, componentes compostos e props bem definidas.</p>
<p>Na prática, isso significa que você consegue manter uma aplicação complexa com dezenas ou centenas de componentes de forma escalável, permitindo mudanças globais de branding em minutos e garantindo que todos os usuários, independentemente de suas capacidades, possam usar sua aplicação. O investimento inicial em construir um bom design system economiza tempo exponencialmente em desenvolvimento futuro.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://www.w3.org/WAI/WCAG21/quickref/" target="_blank" rel="noopener noreferrer">WCAG 2.1 Guidelines - W3C</a></li>
<li><a href="https://react.dev/reference/react/useContext" target="_blank" rel="noopener noreferrer">React Documentation - Context API</a></li>
<li><a href="https://storybook.js.org/" target="_blank" rel="noopener noreferrer">Storybook - Component Driven Development</a></li>
<li><a href="https://www.figma.com/community/file/1184664993926163221" target="_blank" rel="noopener noreferrer">Design Tokens - Figma Guide</a></li>
<li><a href="https://styled-components.com/docs" target="_blank" rel="noopener noreferrer">Styled Components Documentation</a></li>
</ul>
<p><!-- FIM --></p>