Files
Hayden Bleasel 55fae375a4 Update docs, internationalization (#35)
* Update Geistdocs

* Update geistdocs.tsx

* Run translation script

* Delete toc.tsx

* Update route.ts

* Update route.ts
2025-12-04 23:57:35 -08:00

280 lines
9.0 KiB
Plaintext

---
title: Estilização
description: Estilização condicional e composável com classes Tailwind.
---
Bibliotecas de componentes modernas precisam de sistemas de estilização flexíveis que consigam lidar com requisitos complexos sem sacrificar a experiência do desenvolvedor. A combinação do Tailwind CSS com mesclagem inteligente de classes emergiu como um padrão poderoso para construir componentes personalizáveis.
Essa abordagem resolve a tensão fundamental entre fornecer padrões sensatos e permitir a personalização completa — um desafio que atormenta bibliotecas de componentes há anos.
## O problema com a estilização tradicional
Abordagens tradicionais de CSS frequentemente levam a guerras de especificidade, conflitos de estilo e sobrescritas imprevisíveis. Quando você passa `className="bg-blue-500"` para um componente que já tem `bg-red-500`, qual deles vence?
Sem um tratamento adequado, ambas as classes se aplicam e o resultado depende de muitos fatores — ordem de origem do CSS, especificidade das classes, o algoritmo de mesclagem de classes do bundler, etc.
## Mesclando classes inteligentemente
A biblioteca `tailwind-merge` resolve isso entendendo a estrutura de classes do Tailwind e resolvendo conflitos de forma inteligente. Quando duas classes miram a mesma propriedade CSS, ela mantém apenas a última.
```tsx title="Sem tailwind-merge"
// Both bg-red-500 and bg-blue-500 apply - unpredictable result
<Button className="bg-blue-500" />
// Renders: className="bg-red-500 bg-blue-500"
```
```tsx title="Com tailwind-merge"
import { twMerge } from 'tailwind-merge';
// bg-blue-500 wins as it comes last
const className = twMerge('bg-red-500', 'bg-blue-500');
// Returns: "bg-blue-500"
```
Isto funciona para todas as utilitárias do Tailwind:
```tsx
twMerge('px-4 py-2', 'px-8'); // Returns: "py-2 px-8"
twMerge('text-sm', 'text-lg'); // Returns: "text-lg"
twMerge('hover:bg-red-500', 'hover:bg-blue-500'); // Returns: "hover:bg-blue-500"
```
A biblioteca também compreende o sistema de modificadores do Tailwind:
```tsx
// Modifiers are handled correctly
twMerge('hover:bg-red-500 focus:bg-red-500', 'hover:bg-blue-500');
// Returns: "focus:bg-red-500 hover:bg-blue-500"
```
## Classes condicionais
Frequentemente você precisa aplicar classes condicionalmente com base em props ou estado. A biblioteca `clsx` fornece uma API limpa para isso:
```tsx title="Usando clsx"
import clsx from 'clsx';
// Basic conditionals
clsx('base', isActive && 'active');
// Returns: "base active" (if isActive is true)
// Object syntax
clsx('base', {
'active': isActive,
'disabled': isDisabled,
});
// Arrays
clsx(['base', isLarge ? 'text-lg' : 'text-sm']);
// Mixed
clsx(
'base',
['array-item'],
{ 'object-conditional': true },
isActive && 'conditional'
);
```
Um padrão comum é mesclar um conjunto padrão de classes com as props recebidas, assim como qualquer lógica personalizada que tenhamos:
```tsx title="component.tsx"
const Component = ({ className, ...props }: ComponentProps) => {
const [isOpen, setIsOpen] = useState(false);
return (
<div
className={cn(
"rounded-lg border bg-white shadow-sm",
isOpen && "bg-blue-500",
className
)}
{...props}
/>
);
};
```
## A função utilitária `cn`
A função `cn`, popularizada por [shadcn/ui](https://ui.shadcn.com/), combina `clsx` e `tailwind-merge` para fornecer tanto lógica condicional quanto mesclagem inteligente:
```tsx title="lib/utils.ts"
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
```
O poder vem da ordem — estilos base primeiro, condicionais em segundo, sobrescritas do usuário por último. Isso garante comportamento previsível enquanto mantém total personalização.
## Class Variance Authority (CVA)
Para componentes complexos com muitas variantes, gerenciar manualmente classes condicionais se torna impraticável. [Class Variance Authority (CVA)](https://cva.style/docs) fornece uma API declarativa para definir variantes de componente.
Por exemplo, aqui está um trecho do componente [Button](https://ui.shadcn.com/docs/components/button) do shadcn/ui:
```tsx title="@/components/ui/button.tsx"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
```
## Melhores práticas
### 1. A ordem importa
Sempre aplique classes nesta ordem:
1. Estilos base (sempre aplicados)
2. Estilos de variante (baseados em props)
3. Estilos condicionais (baseados em estado)
4. Sobrescritas do usuário (prop className)
```tsx
className={cn(
'base-styles', // 1. Base
variant && variantStyles, // 2. Variants
isActive && 'active', // 3. Conditionals
className // 4. User overrides
)}
```
### 2. Documente suas variantes
Use TypeScript e JSDoc para documentar o que cada variante faz:
```tsx
type ButtonProps = {
/**
* The visual style of the button
* @default "primary"
*/
variant?: 'primary' | 'secondary' | 'destructive' | 'ghost';
/**
* The size of the button
* @default "md"
*/
size?: 'sm' | 'md' | 'lg';
};
```
### 3. Extraia padrões repetidos
Se você perceber que está escrevendo a mesma lógica condicional repetidamente, extraia-a:
```tsx title="utils/styles.ts"
export const focusRing = 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500';
export const disabled = 'disabled:pointer-events-none disabled:opacity-50';
// Use in components
className={cn(focusRing, disabled, className)}
```
## Guia de migração
Se você está migrando de uma abordagem de estilização diferente, aqui está como adaptar padrões comuns:
### Do CSS Modules
```tsx title="Before - CSS Modules"
import styles from './Button.module.css';
<button className={`${styles.button} ${styles[variant]} ${className}`} />
```
```tsx title="Depois - cn + Tailwind"
import { cn } from '@/lib/utils';
<button className={cn(
'px-4 py-2 rounded-lg',
variant === 'primary' && 'bg-blue-500 text-white',
className
)} />
```
### Do styled-components
```tsx title="Before - styled-components"
const Button = styled.button<{ $primary?: boolean }>`
padding: 8px 16px;
background: ${props => props.$primary ? 'blue' : 'gray'};
`;
```
```tsx title="Depois - cn + Tailwind"
function Button({ primary, className, ...props }) {
return (
<button
className={cn(
'px-4 py-2',
primary ? 'bg-blue-500' : 'bg-gray-500',
className
)}
{...props}
/>
);
}
```
## Considerações de desempenho
Tanto `clsx` quanto `tailwind-merge` são altamente otimizados, mas tenha estas dicas em mente:
1. Defina variantes fora dos componentes - as variantes do CVA devem ser definidas fora do componente para evitar recriação em cada renderização.
2. Memoize computações complexas - se você tiver lógica condicional custosa, considere memoizar:
```tsx
const className = useMemo(
() => cn(
baseStyles,
expensiveComputation(props),
className
),
[props, className]
);
```
3. Use variáveis CSS para valores dinâmicos - em vez de gerar classes dinamicamente, use variáveis CSS:
```tsx title="Prefer CSS variables"
// Good
<div
className="bg-[var(--color)]"
style={{ '--color': dynamicColor } as React.CSSProperties}
/>
// Avoid
<div className={`bg-[${dynamicColor}]`} />
```
A combinação de Tailwind CSS, mesclagem inteligente de classes e APIs de variantes fornece uma base robusta para estilização de componentes. Essa abordagem escala desde botões simples até sistemas de design complexos, mantendo previsibilidade e uma boa experiência do desenvolvedor.