Estilizar aplicações React de maneira moderna, eficiente e modular, utilizando a biblioteca Styled-Components. Essa abordagem é baseada em CSS-in-JS, permitindo que o CSS seja escrito diretamente dentro dos componentes JavaScript.
- Framework x Biblioteca
- HTML e CSS
- Introdução ao React
- Componentes, propriedades e estados
- Introdução aos hooks
- Hook
useState
- Hook
useEffect
- React Router*
Instale as dependências do projeto
yarn
Execute o projeto em um servidor de desenvolvimento
yarn dev
O styled-components é uma biblioteca para React e React Native que permite estilizar componentes usando uma abordagem baseada em JavaScript e CSS. Ele utiliza tagged template literals para escrever estilos diretamente em arquivos JavaScript, encapsulando o CSS em componentes.
import styled from 'styled-components';
const Botao = styled.button`
background-color: ${(props) => props.primary ? 'blue' : 'gray'};
color: white;
padding: 10px;
`;
Isso facilita a criação de estilos utilizando valores dinâmicos, reutilizáveis e elimina problemas como conflitos de nomes de seletores no CSS tradicional. Além disso, ele facilita a criação de temas na aplicação (exemplo, dark e light).
Uma das motivações do styled-components foi aprimorar a experiência para nós desenvolvedores no momento de estilizarmos nossos componentes React. Além disso, a biblioteca oferece:
- CSS crítico automático: o styled-components rastreia quais componentes são renderizados em uma página e injeta apenas seus estilos, sem nada além disso, de forma totalmente automática, fazendo com que os usuários carreguem a menor quantidade de código necessária.
- Sem bugs de nomes de classes: o styled-components gera nomes de classes únicos para seus estilos. Você nunca precisa se preocupar com duplicações, sobreposições ou erros de digitação.
- Exclusão facilitada de CSS: pode ser difícil saber se um nome de classe está sendo usado em algum lugar da base de código. O styled-components torna isso evidente, já que cada estilo está vinculado a um componente específico. Se o componente não estiver mais em uso (o que ferramentas podem detectar) e for excluído, todos os seus estilos também serão removidos.
- Estilização dinâmica simplificada: adaptar os estilos de um componente com base em suas props ou em um tema global é simples e intuitivo, sem a necessidade de gerenciar manualmente dezenas de classes.
- Manutenção sem complicações: você nunca precisará procurar em arquivos diferentes para encontrar o estilo que está afetando seu componente, tornando a manutenção muito mais fácil, independentemente do tamanho da base de código.
- Prefixos automáticos para navegadores: escreva seu CSS de acordo com o padrão atual, e o styled-components cuidará do restante.
# com npm
npm install styled-components
# com yarn
yarn add styled-components
Se você estiver usando o Yarn, ele tem um recurso chamado resolutions
, que permite forçar o uso de uma versão específica de uma biblioteca em todo o projeto. Isso é útil porque:
- Em projetos grandes, pode acontecer de dependências diferentes tentarem usar versões diferentes do styled-components.
- Essa configuração garante que todo o projeto utilize a mesma versão principal (no exemplo, a versão 5).
Exemplo de configuração no arquivo package.json
:
{
"resolutions": {
"styled-components": "^5"
}
}
O styled-components utiliza o que chamamos de tagged template literals para estilizar seus componentes.
ℹ️Tagged Template Literals são uma funcionalidade do JavaScript que permite combinar strings escritas com crases (```` que chamamos de template literals**)** com uma função especial. Essa função consegue manipular o conteúdo da string antes de ela ser usada, incluindo os textos fixos e os valores que você insere dentro da string.
O styled-components elimina a necessidade de criar uma separação entre o CSS e os componentes no React. Em vez de escrever estilos em um arquivo CSS separado e aplicar classes, você cria diretamente um componente estilizado que já tem os estilos incorporados.
// Cria um componente Title que renderizará uma tag <h1> com alguns estilos
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: #BF4F74;
`;
// Cria um componente Wrapper que renderizará uma tag <section> com alguns estilos
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;
// Use Title e Wrapper como qualquer outro componente React – só que estilizados!
render(
<Wrapper>
<Title>
Hello World!
</Title>
</Wrapper>
);
Quando você cria um componente estilizado (usando, por exemplo, a biblioteca Styled Components no React), você pode personalizar o estilo desse componente com base nas propriedades que você passa para o componente.
const Button = styled.button<{ $primary?: boolean; }>`
/* Adapt the colors based on primary prop */
background: ${props => props.$primary ? "#BF4F74" : "white"};
color: ${props => props.$primary ? "white" : "#BF4F74"};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid #BF4F74;
border-radius: 3px;
`;
render(
<div>
<Button>Normal</Button>
<Button $primary>Primary</Button>
</div>
);
Às vezes, você pode querer usar um componente, mas fazer algumas mudanças nele para um caso específico. Em vez de passar muitas funções para alterar o estilo, você pode criar um novo componente facilmente.
Para isso, basta usar o styled()
e "herdar" o estilo de um componente existente.
// O botão da seção anterior sem as interpolations
const Button = styled.button`
color: #BF4F74;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid #BF4F74;
border-radius: 3px;
`;
// Um novo componente baseado no Button, mas com alguns estilos sobrescritos
const TomatoButton = styled(Button)`
color: tomato;
border-color: tomato;
`;
render(
<div>
<Button>Botão Normal</Button>
<TomatoButton>Botão Tomato</TomatoButton>
</div>
);
Quando você cria um componente estilizado, ele normalmente renderiza uma tag HTML específica, como <button>
, <a>
, <div>
, etc. Mas, em alguns casos, você pode querer que o componente renderize uma tag diferente, sem mudar o estilo dele. Por exemplo, em uma barra de navegação, você pode ter tanto links (<a>
) quanto botões (<button>
), mas quer que eles pareçam e se comportem de forma igual, com o mesmo estilo.
A solução para isso é usar a propriedade as
. Ela permite que você escolha qual tag ou componente o styled-components vai renderizar, sem precisar alterar o estilo.
const Button = styled.button`
display: inline-block;
color: #BF4F74;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid #BF4F74;
border-radius: 3px;
display: block;
`;
const TomatoButton = styled(Button)`
color: tomato;
border-color: tomato;
`;
render(
<div>
<Button>Normal Button</Button>
<Button as="a" href="#">Link with Button styles</Button>
<TomatoButton as="a" href="#">Link with Tomato Button styles</TomatoButton>
</div>
);
Esse recurso funciona perfeitamente com componentes customizados também!
const Button = styled.button`
display: inline-block;
color: #BF4F74;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid #BF4F74;
border-radius: 3px;
display: block;
`;
const ReversedButton = props => <Button {...props} children={props.children.split('').reverse()} />
render(
<div>
<Button>Normal Button</Button>
<Button as={ReversedButton}>Custom Button with Normal Button styles</Button>
</div>
);
O método styled
funciona perfeitamente em qualquer componente, seja seu ou de terceiros, desde que o componente passe a prop className
para um elemento DOM.
// Isso poderia ser o Link do react-router, por exemplo
const Link = ({ className, children }) => (
<a className={className}>
{children}
</a>
);
const StyledLink = styled(Link)`
color: #BF4F74;
font-weight: bold;
`;
render(
<div>
<Link>Unstyled, boring Link</Link>
<br />
<StyledLink>Styled, exciting Link</StyledLink>
</div>
);
Você também pode passar nomes de tags na chamada ao styled()
, assim: styled("div")
. Na verdade, a sintaxe styled.tagname
são apenas formas de escrever que fazem a mesma coisa.
Se o que for passado para o styled
for um elemento simples (por exemplo, styled.div
), o styled-components
passa qualquer atributo HTML conhecido para o DOM. Se for um componente React personalizado (por exemplo, styled(MyComponent)
), o styled-components
passa todas as props do seu componene.
// Cria um componente Input que renderiza uma tag <input> com alguns estilos
const Input = styled.input<{ $inputColor?: string; }>`
padding: 0.5em;
margin: 0.5em;
color: ${props => props.$inputColor || "#BF4F74"};
background: papayawhip;
border: none;
border-radius: 3px;
`;
// Renderiza um campo de texto estilizado com a cor de entrada padrão,
// e outro com uma cor personalizada
render(
<div>
<Input defaultValue="@probablyup" type="text" />
<Input defaultValue="@geelen" type="text" $inputColor="rebeccapurple" />
</div>
);
Este exemplo mostra como todas as props do componente Input
são passadas para o nó do DOM que é montado, assim como nos elementos React.
No Styled Components, o uso do &
e &&
tem funções relacionadas ao CSS dentro do JavaScript, mas com diferenças sutis na maneira como eles são aplicados.
O &
é um seletor especial que faz referência ao próprio componente ou à classe que está sendo estilizada. Ele é usado para adicionar mais especificidade ou para referenciar o estado do componente, como quando você deseja aplicar estilos para um estado de hover, foco, ou outros pseudo-seletores.
const Button = styled.button`
background-color: blue;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
/* Pseudo-seletor: estado de hover */
&:hover {
background-color: green; /* Aplica quando o botão for hover */
cursor: pointer; /* Muda o cursor ao passar o mouse */
}
/* Pseudo-seletor: quando o botão está ativo (pressionado) */
&:active {
background-color: darkgreen; /* Aplica quando o botão for pressionado */
}
/* Pseudo-classe: foco no botão */
&:focus {
outline: 2px solid orange; /* Aplica uma borda laranja ao focar no botão */
}
/* Usando a classe ".active" */
&.active {
background-color: red; /* Aplica quando o botão tiver a classe "active" */
}
/* Pseudo-elemento: antes do conteúdo do botão */
&::before {
content: '👉'; /* Adiciona um emoji antes do texto */
margin-right: 8px; /* Espaçamento entre o ícone e o texto */
}
/* Pseudo-elemento: depois do conteúdo do botão */
&::after {
content: '❗'; /* Adiciona um ícone depois do texto */
margin-left: 8px; /* Espaçamento entre o texto e o ícone */
}
/* Aninhamento de um elemento interno dentro do botão */
span {
font-weight: bold;
font-size: 1.1rem;
}
/* Estilo quando o botão está desabilitado */
&:disabled {
background-color: lightgray;
cursor: not-allowed; /* Indica que o botão não pode ser clicado */
}
`;
- O
&:hover
se refere ao próprio componente (Button
) quando está em estado de hover. - O
&.active
se refere ao componenteButton
com a classe.active
.
O &&
é uma maneira de aumentar a especificidade dos seletores, ou seja, tornar os estilos aplicados mais "fortes" e garantir que sejam aplicados com uma especificidade mais alta do que as regras que podem vir antes ou em outros lugares. Ele é comumente usado para evitar que estilos sejam sobrescritos por outras regras CSS mais genéricas ou com menor especificidade.
const Button = styled.button`
background-color: blue;
&& {
background-color: red; /* Aplica a regra com mais especificidade */
}
`;
O &&
aqui cria um seletor de maior especificidade, o que torna difícil para outras regras CSS sobrescreverem essa.
Permite que você passe props para um componente estilizado de maneira dinâmica, com base no que é necessário em um momento específico.
Vamos criar um botão (Button
) que aceita uma prop dinâmica para ajustar a largura do botão.
import styled from 'styled-components';
const Button = styled.button.attrs<{ $width?: string }> (props => ({
// Definindo uma prop estática
type: "button",
// Definindo uma prop dinâmica
$width: props.$width || "150px", // Se não passar, o valor padrão será "150px"
}))`
background-color: #4CAF50;
color: white;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
/* Usando a prop dinâmica para ajustar a largura do botão */
width: ${props => props.$width};
padding: 10px;
/* Estilos para o hover */
&:hover {
background-color: #45a049;
}
/* Estilos para o foco */
&:focus {
outline: none;
}
`;
const App = () => {
return (
<div>
<Button onClick={() => alert('Button clicked!')}>
Default Button
</Button>
<br />
<Button $width="300px" onClick={() => alert('Custom width button clicked!')}>
Custom Width Button
</Button>
</div>
);
};
export default App;
attrs
: A função attrs
é usada para definir props estáticas e dinâmicas. No caso, usamos:
- A prop estática
type: "button"
. - A prop dinâmica
$width
, que recebe um valor passado para o componente ou um valor padrão de"150px"
caso não seja fornecido.
Por que definimos uma propriedade estática nesse caso?
Isso é importante para evitar comportamentos indesejados, como o envio acidental de um formulário quando você só quer que o botão execute uma função específica com um manipulador de eventos (como onClick
).
Quando você está criando um botão estilizado com o styled-components, pode ser uma boa prática especificar explicitamente o tipo de botão, principalmente se o botão estiver dentro de um formulário, para evitar comportamentos automáticos indesejados.
Exemplo:
- Dentro de um formulário: Se você não definir o tipo como
button
, o botão pode acabar enviando o formulário automaticamente (comportamento padrão dotype="submit"
). - Com comportamento personalizado: Definir
type="button"
garante que o botão não faça nada por conta própria, permitindo que você defina claramente sua funcionalidade com JavaScript ou React.
Você pode sobrescrever atributos e estilos de um componente estilizado, utilizando o conceito de herança no styled-components
e o uso da função .attrs
para adicionar ou modificar atributos em componentes baseados em outros.
import React from 'react';
import styled from 'styled-components';
// Componente base: Input
const Input = styled.input.attrs<{ $size?: string }>((props) => ({
type: 'text', // Atributo estático
$size: props.$size || '1em', // Prop dinâmica com valor padrão
}))`
border: 2px solid #bf4f74;
margin: ${(props) => props.$size};
padding: ${(props) => props.$size};
`;
// Componente PasswordInput que sobrescreve os atributos e estilos do Input
const PasswordInput = styled(Input).attrs({
type: 'password', // Sobrescreve o tipo do Input para "password"
})`
border: 2px solid aqua; // Sobrescreve o estilo de borda do Input
/* Você também pode adicionar novos estilos específicos ao PasswordInput */
background-color: #f0f0f0;
font-size: 1.2em;
`;
const App = () => {
return (
<div>
{/* Usando o Input base */}
<Input placeholder="A bigger text input" $size="2em" />
<br />
{/* Usando o PasswordInput com atributos sobrescritos */}
<PasswordInput placeholder="A bigger password input" $size="2em" />
</div>
);
};
export default App;
No CSS, animações são definidas usando a regra @keyframes
, que permite criar efeitos de movimento ou transição para os elementos da página. No entanto, quando você define uma animação com @keyframes
, ela não está restrita a um componente específico — ou seja, ela pode ser aplicada a qualquer elemento na página, o que pode gerar conflitos de nomes se houver animações com o mesmo nome.
O styled-components oferece o helper keyframes
, que faz com que o nome da animação seria único para cada componente, garantindo que não haja conflito, mesmo que você use o mesmo nome:
// Crie os keyframes
const rotate = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
// Aqui criamos um componente que vai rotacionar tudo o que passarmos para
// ele em dois segundos
const Rotate = styled.div`
display: inline-block;
animation: ${rotate} 2s linear infinite;
padding: 2rem 1rem;
font-size: 1.2rem;
`;
render(
<Rotate>< 💅🏾 ></Rotate>
);
No styled-components
, os temas permitem que você centralize e gerencie valores compartilhados, como cores, espaçamentos, fontes, e outros estilos que podem ser usados em toda a aplicação. Isso facilita a criação de uma interface consistente e a alternância entre diferentes temas, como um tema claro e um tema escuro.
styled-components: Advanced Usage
O ThemeProvider
é o componente responsável por fornecer o tema para toda a sua aplicação. Você define o tema como um objeto, contendo as propriedades que deseja compartilhar.
export const lightTheme = {
colors: {
primary: '#6200ee',
background: '#ffffff',
text: '#000000',
},
spacing: {
small: '8px',
medium: '16px',
large: '24px',
},
};
export const darkTheme = {
colors: {
primary: '#bb86fc',
background: '#121212',
text: '#ffffff',
},
spacing: {
small: '8px',
medium: '16px',
large: '24px',
},
};
Para aplicar o tema com os valores configurados abaixo, você pode utilizar o ThemeProvider
em volta da sua aplicação:
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { lightTheme } from './theme';
import styled from 'styled-components';
const Container = styled.div`
background-color: ${(props) => props.theme.colors.background};
color: ${(props) => props.theme.colors.text};
padding: ${(props) => props.theme.spacing.medium};
`;
function App() {
return (
<ThemeProvider theme={lightTheme}>
<Container>
Olá, styled-components com temas!
</Container>
</ThemeProvider>
);
}
export default App;
Para alternar entre os temas, você pode controlar o valor passado ao ThemeProvider
através de um estado no React:
import React, { useState } from 'react';
import { ThemeProvider } from 'styled-components';
import { lightTheme, darkTheme } from './theme';
import styled from 'styled-components';
const Container = styled.div`
background-color: ${(props) => props.theme.colors.background};
color: ${(props) => props.theme.colors.text};
padding: ${(props) => props.theme.spacing.medium};
`;
const Button = styled.button`
background: ${(props) => props.theme.colors.primary};
color: ${(props) => props.theme.colors.text};
border: none;
padding: ${(props) => props.theme.spacing.small};
margin-top: ${(props) => props.theme.spacing.medium};
cursor: pointer;
`;
function App() {
const [isDarkTheme, setIsDarkTheme] = useState(false);
const toggleTheme = () => setIsDarkTheme(!isDarkTheme);
return (
<ThemeProvider theme={isDarkTheme ? darkTheme : lightTheme}>
<Container>
<h1>Alternância de Temas</h1>
<Button onClick={toggleTheme}>
{isDarkTheme ? 'Mudar para Tema Claro' : 'Mudar para Tema Escuro'}
</Button>
</Container>
</ThemeProvider>
);
}
export default App;
Através do styled-components é possível criar temas dinâmicos ao passar uma função na propriedade theme
para o ThemeProvider
. Essa função tem acesso ao tema pai (fornecido por um ThemeProvider
superior na árvore de componentes) e pode gerar um novo tema com base nele.
Essa funcionalidade é extremamente útil quando você deseja criar variações contextuais de um tema, como inverter as cores, ajustar propriedades, ou até mesmo combinar temas existentes.
Quando você passa uma função como o valor da propriedade theme
no ThemeProvider
, essa função recebe o tema atual fornecido pelo ThemeProvider
mais acima na hierarquia de componentes. A partir disso, ela pode criar um novo tema.
import React from 'react';
import styled, { ThemeProvider } from 'styled-components';
const Button = styled.button`
background: ${(props) => props.theme.background};
color: ${(props) => props.theme.color};
border: 2px solid ${(props) => props.theme.border};
padding: 10px 20px;
cursor: pointer;
`;
// Tema inicial
const defaultTheme = {
background: 'white',
color: 'black',
border: 'black',
};
// Função que inverte as cores
const invertTheme = ({ color, background, border }) => ({
background: color,
color: background,
border: border,
});
function App() {
return (
<ThemeProvider theme={defaultTheme}>
<div>
<h1>Function Themes</h1>
{/* Primeiro botão usa o tema padrão */}
<Button>Botão Padrão</Button>
{/* Segundo botão usa um tema invertido */}
<ThemeProvider theme={invertTheme}>
<Button>Botão Invertido</Button>
</ThemeProvider>
</div>
</ThemeProvider>
);
}
export default App;
Neste exemplo acima, nós temos a seguinte situação:
- O
ThemeProvider
raiz passa odefaultTheme
para todos os componentes filhos. O primeiro botão utiliza as cores padrão (background: white
,color: black
). - O segundo
ThemeProvider
encapsula apenas o segundo botão e passa uma função (invertTheme
) para a propriedadetheme
. - O
invertTheme
recebe o tema atual doThemeProvider
pai (defaultTheme
) e cria um novo tema invertido: trocando as cores debackground
ecolor
. - O tema invertido não altera o tema global. Ele afeta apenas os componentes que estão dentro de seu próprio
ThemeProvider
.
Imagine que queremos adicionar transparência ao tema pai para criar um efeito sobreposto (como em modais):
import React from 'react';
import styled, { ThemeProvider } from 'styled-components';
const Container = styled.div`
background: ${(props) => props.theme.background};
color: ${(props) => props.theme.color};
padding: 20px;
text-align: center;
`;
// Tema padrão
const defaultTheme = {
background: '#ffffff',
color: '#333333',
};
// Função para adicionar transparência ao tema pai
const transparentTheme = (theme) => ({
...theme,
background: 'rgba(0, 0, 0, 0.7)', // Sobrepõe o fundo com transparência
color: '#ffffff', // Texto claro para contraste
});
function App() {
return (
<ThemeProvider theme={defaultTheme}>
<Container>
<h1>Tema Principal</h1>
<ThemeProvider theme={transparentTheme}>
<Container>
<h2>Tema com Transparência</h2>
</Container>
</ThemeProvider>
</Container>
</ThemeProvider>
);
}
export default App;
O tema de transparência (transparentTheme
) herda e modifica o tema padrão, criando um efeito visual diferente sem duplicar código. O componente interno usa rgba(0, 0, 0, 0.7)
para um fundo semitransparente, enquanto mantém o estilo geral do tema pai.
O useTheme
é um hook fornecido pelo styled-components
para acessar o tema atual diretamente dentro de um componente funcional. Isso é útil para componentes que não têm acesso direto às props do tema.
import React from 'react';
import { ThemeProvider, useTheme } from 'styled-components';
import { lightTheme } from './theme';
import styled from 'styled-components';
const Container = styled.div`
background-color: ${(props) => props.theme.colors.background};
color: ${(props) => props.theme.colors.text};
padding: ${(props) => props.theme.spacing.medium};
`;
const CustomButton = styled.button`
background: ${(props) => props.theme.colors.primary};
color: ${(props) => props.theme.colors.text};
border: none;
padding: ${(props) => props.theme.spacing.small};
cursor: pointer;
`;
function ChildComponent() {
const theme = useTheme();
return (
<div>
<p>Cor de fundo atual: {theme.colors.background}</p>
<CustomButton>Botão estilizado</CustomButton>
</div>
);
}
function App() {
return (
<ThemeProvider theme={lightTheme}>
<Container>
<h1>Hook useTheme</h1>
<ChildComponent />
</Container>
</ThemeProvider>
);
}
export default App;
Você pode informar um tema diretamente para um componente. Essa abordagem pode ser útil em situações onde:
- Não há um
ThemeProvider
disponível no contexto da aplicação, mas você ainda deseja usar o sistema de temas. - Você deseja sobrescrever o tema global (definido no
ThemeProvider
) em um componente específico.
// Tema customizado
const customTheme = {
background: '#6200ee',
color: '#ffffff',
};
...
<Button theme={customTheme}>Botão com tema customizado</Button>
O styled-components suporta tanto a escrita de código CSS como strings, quanto um objeto JavaScript. Esse recurso é útil quando você tem objetos de estilo existentes e deseja migrar gradualmente para componentes estilizados.
// Objeto estático
const Box = styled.div<{ $background?: string; }>({
background: '#BF4F74',
height: '50px',
width: '50px'
});
// Adaptando com base nas props
const PropsBox = styled.div(props => ({
background: props.$background,
height: '50px',
width: '50px'
}));
render(
<div>
<Box />
<PropsBox $background="blue" />
</div>
);