Skip to main content

📘 Componentes e Estilos (CSS)

Nos capítulos anteriores, aprendemos a construir aplicações React utilizando componentes, props e states.

Já conseguimos criar componentes reutilizáveis, transmitir informações entre eles e desenvolver interfaces capazes de reagir às ações do usuário.

Agora avançaremos para outro aspecto fundamental do desenvolvimento Front-End: a apresentação visual da aplicação.

Neste capítulo, utilizaremos CSS (Cascading Style Sheets) para estilizar os componentes React desenvolvidos anteriormente.


Separando estrutura, comportamento e apresentação​

Durante o desenvolvimento de uma aplicação React, podemos pensar em três aspectos principais:

JSX
│
└── Estrutura da interface

JavaScript / React
│
└── Comportamento e estado

CSS
│
└── Apresentação visual

Nos componentes que desenvolveremos, os arquivos .jsx continuarão responsáveis pela estrutura e pelo comportamento, enquanto arquivos .css serão utilizados para definir características visuais.

Por exemplo:

Label.jsx
│
└── importa
│
▼
Label.css
CSS

CSS (Cascading Style Sheets) é a linguagem utilizada para definir a apresentação visual de documentos HTML.

Com CSS podemos controlar, entre outras características:

  • cores;
  • tamanhos;
  • fontes;
  • margens;
  • espaçamentos;
  • bordas;
  • alinhamentos;
  • posicionamento;
  • efeitos visuais.

Estrutura do projeto

Continuaremos utilizando a aplicação de login desenvolvida nos capítulos anteriores.

A organização será:

src/
│
├── main.jsx
├── App.jsx
│
└── componentes/
└── CSS/
├── Login.jsx
├── Login.css
├── Label.jsx
├── Label.css
├── LabelTitle.jsx
├── LabelTitle.css
├── InputText.jsx
├── InputText.css
├── InputPassword.jsx
├── InputPassword.css
├── Button.jsx
└── Button.css

O fluxo principal da aplicação permanece:

index.html
│
▼
main.jsx
│
▼
App.jsx
│
▼
Login.jsx
│
├── LabelTitle.jsx ─────► LabelTitle.css
├── Label.jsx ──────────► Label.css
├── InputText.jsx ──────► InputText.css
├── InputPassword.jsx ──► InputPassword.css
└── Button.jsx ─────────► Button.css
│
▼
Login.css

Observe que agora cada componente pode importar sua própria folha de estilos.


Importando CSS em um componente React

Uma folha de estilos pode ser importada diretamente pelo componente:

import "./Label.css";

Ao realizar essa importação, as regras definidas no arquivo CSS passam a fazer parte dos estilos carregados pela aplicação.

Para associar uma classe CSS a um elemento JSX utilizamos:

className="nome-da-classe"

Por exemplo:

<label className="label">
Usuário:
</label>
class x className

No HTML tradicional utilizamos:

<label class="label">

Em JSX utilizamos:

<label className="label">

Portanto, ao escrever componentes React, utilize className para associar classes CSS aos elementos.


Estilizando o componente Label

O componente Label continuará recebendo o texto por meio de uma prop e mantendo esse valor em um estado.

A diferença é que agora adicionaremos uma classe CSS ao elemento <label>.

Label.jsx​

import "./Label.css";
import React, { useState } from "react";

export default function Label({ texto }) {

const [valor, setValor] = useState(texto);

return (
<label className="label">
{valor}
</label>
);
}

Observe:

className="label"

Essa propriedade associa o elemento à classe:

.label

definida em Label.css.


Label.css​

.label {
display: block;
font-size: 1.2rem;
margin-bottom: 0.3rem;
color: #333;
}

Vamos analisar cada propriedade.

PropriedadeFunção
display: blockFaz o elemento ocupar uma linha como bloco
font-size: 1.2remDefine o tamanho da fonte
margin-bottom: 0.3remCria um espaço abaixo do rótulo
color: #333Define a cor do texto

display​

display: block;

Faz com que o elemento <label> seja apresentado como bloco.

Isso facilita a organização vertical:

Usuário:
[ campo de entrada ]

font-size​

font-size: 1.2rem;

Define o tamanho da fonte utilizando a unidade relativa rem.

Unidades relativas

O uso de unidades relativas, como rem, permite que os tamanhos acompanhem melhor as configurações de fonte do navegador.

Isso contribui para a escalabilidade da interface.

margin-bottom​

margin-bottom: 0.3rem;

Cria um pequeno espaço abaixo do elemento.

Podemos representar:

Usuário:
↕ espaço
[ input ]

color​

color: #333;

Define uma tonalidade de cinza escuro para o texto.


Criando um título para o formulário

No capítulo anterior utilizamos componentes para apresentar os diferentes textos da interface.

Agora criaremos um componente específico para o título:

LabelTitle.jsx

Isso permite aplicar uma estilização diferente ao título da tela.

LabelTitle.jsx​

import "./LabelTitle.css";
import React, { useState } from "react";

export default function LabelTitle({ texto }) {

const [valor, setValor] = useState(texto);

return (
<label className="labelTitle">
{valor}
</label>
);
}

Sua estrutura é semelhante ao Label.jsx.

A principal diferença está na classe:

className="labelTitle"

LabelTitle.css​

.labelTitle {
display: block;
font-size: 1.9rem;
margin-bottom: 0.3rem;
color: #004999;
}

Temos:

PropriedadeFunção
display: blockFaz o título ocupar a linha
font-size: 1.9remAumenta o destaque do título
margin-bottom: 0.3remCria espaçamento inferior
color: #004999Define a cor azul do título

Observe a diferença entre os dois componentes:

Label
└── font-size: 1.2rem
└── color: #333

LabelTitle
└── font-size: 1.9rem
└── color: #004999

Isso cria uma hierarquia visual.

O usuário consegue perceber rapidamente que:

Login          ← título

Usuário: ← rótulo
[ input ]

Senha: ← rótulo
[ input ]
Consistência visual

Manter uma paleta coerente de cores e tamanhos ajuda a criar uma identidade visual consistente para a aplicação.


Estilizando o campo de texto

Agora aplicaremos CSS ao componente InputText.

O comportamento estudado anteriormente será mantido:

  • estado para armazenar o valor;
  • estado para indicar erro;
  • evento onChange;
  • validação;
  • renderização condicional.

Acrescentaremos apenas sua apresentação visual.

InputText.jsx​

import "./InputText.css";
import React, { useState } from "react";

export default function InputText({ placeholder }) {

const [valor, setValor] = useState("");
const [erro, setErro] = useState(false);

function handleChange(e) {

const novoValor = e.target.value;

setValor(novoValor);
setErro(novoValor.trim() === "");
}

return (
<>
<input
type="text"
placeholder={placeholder}
value={valor}
onChange={handleChange}
/>

{erro && <p>⚠️ O campo não pode ficar vazio.</p>}
</>
);
}

InputText.css​

input[type="text"] {
width: 100%;
padding: 0.6rem;
font-size: 0.95rem;
border: 1px solid #ccc;
border-radius: 4px;
outline: none;
transition: border-color 0.3s ease;
box-sizing: border-box;
margin-bottom: 0.3rem;
}

input[type="text"]:focus {
border-color: #0066cc;
box-shadow: 0 0 3px rgba(0, 102, 204, 0.4);
}

p {
font-size: 0.85rem;
color: #cc0000;
margin: 0.2rem 0 0;
}

Seletores CSS

Observe:

input[type="text"]

Esse é um seletor de atributo.

Ele seleciona elementos:

<input>

que possuam:

type="text"

Assim:

input
│
└── type="text"
│
▼
input[type="text"]

Largura​

width: 100%;

Faz o input ocupar toda a largura disponível de seu contêiner.


Espaçamento interno​

padding: 0.6rem;

O padding cria espaço entre o conteúdo e as bordas do elemento.

┌──────────────────────────┐
│ ↔ padding │
│ texto digitado │
│ │
└──────────────────────────┘

Bordas​

border: 1px solid #ccc;

Define:

1px     → espessura
solid → estilo
#ccc → cor

Também utilizamos:

border-radius: 4px;

para arredondar levemente os cantos.


box-sizing

Observe:

box-sizing: border-box;

Por padrão, largura, padding e borda podem interferir no tamanho final do elemento.

Com:

box-sizing: border-box;

o navegador considera padding e borda dentro da largura definida.

Isso é especialmente útil quando utilizamos:

width: 100%;

Pseudoclasse :focus

Também temos:

input[type="text"]:focus {
border-color: #0066cc;
box-shadow: 0 0 3px rgba(0, 102, 204, 0.4);
}

A pseudoclasse:

:focus

é aplicada quando o elemento recebe foco.

Por exemplo, quando o usuário clica no campo:

campo normal
│
│ clique
▼
campo com foco
│
▼
:foco visual

A borda passa a utilizar:

border-color: #0066cc;

e recebe uma sombra:

box-shadow: 0 0 3px rgba(0, 102, 204, 0.4);
Feedback visual

Alterações visuais durante o foco ajudam o usuário a identificar qual elemento da interface está ativo.


Transições

Observe:

transition: border-color 0.3s ease;

A propriedade transition faz com que a mudança visual da borda ocorra de forma gradual.

Podemos interpretar:

border-color
│
├── duração: 0.3s
│
└── comportamento: ease

Estilizando o campo de senha

Aplicaremos uma abordagem semelhante ao componente InputPassword.

InputPassword.jsx​

import "./InputPassword.css";
import React, { useState } from "react";

export default function InputPassword({ placeholder }) {

const [senha, setSenha] = useState("");

function handleChange(e) {
setSenha(e.target.value);
}

return (
<>
<input
type="password"
placeholder={placeholder}
value={senha}
onChange={handleChange}
/>

{(senha !== "") && <p>Senha digitada: {senha}</p>}
</>
);
}

InputPassword.css​

input[type="password"] {
width: 100%;
padding: 0.6rem;
font-size: 0.95rem;
border: 1px solid #ccc;
border-radius: 4px;
outline: none;
transition: border-color 0.3s ease;
margin-bottom: 0.3rem;
}

input[type="password"]:focus {
border-color: #28a745;
box-shadow: 0 0 3px rgba(40, 167, 69, 0.4);
}

p {
font-size: 0.85rem;
color: #555;
margin: 0.2rem 0 0;
font-style: italic;
}

O seletor agora é:

input[type="password"]

Portanto, essas regras são aplicadas aos campos cujo tipo seja:

type="password"

Estilo durante o foco​

Quando o campo recebe foco:

input[type="password"]:focus {
border-color: #28a745;
box-shadow: 0 0 3px rgba(40, 167, 69, 0.4);
}

A borda e a sombra utilizam uma tonalidade verde.

O box-shadow cria um destaque visual ao redor do campo.

Exemplo didático

Como estudamos no capítulo anterior, a exibição do conteúdo da senha é utilizada apenas para permitir a visualização da alteração do estado.

Em uma aplicação real, não devemos exibir a senha digitada dessa forma.


Estilizando o botão

O componente Button também continuará utilizando estado e o evento onClick.

Agora adicionaremos:

className="btn"

para permitir sua estilização.

Button.jsx​

import "./Button.css";
import React, { useState } from "react";

export default function Button({ valor }) {

const [texto, setTexto] = useState(valor);

function handleClick() {
setTexto("Você clicou!");
}

return (
<button className="btn" onClick={handleClick}>
{texto}
</button>
);
}

Button.css​

.btn {
width: 100%;
padding: 0.8rem;
background-color: #0066cc;
color: white;
font-size: 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}

.btn:hover {
background-color: #004999;
}

Cor de fundo e texto

A propriedade:

background-color: #0066cc;

define a cor de fundo do botão.

Enquanto:

color: white;

define a cor do texto.


Removendo a borda

border: none;

remove a borda padrão do botão.

Em seguida:

border-radius: 4px;

arredonda seus cantos.


Alterando o cursor

cursor: pointer;

faz com que o navegador apresente o cursor de interação ao passar sobre o botão.

Isso ajuda a indicar visualmente que o elemento pode ser clicado.


Pseudoclasse :hover

Observe:

.btn:hover {
background-color: #004999;
}

A pseudoclasse:

:hover

é aplicada quando o ponteiro do mouse está sobre o elemento.

Temos:

Botão normal
#0066cc
│
│ ponteiro sobre o botão
▼
:hover
│
▼
#004999
Feedback de interação

O efeito hover fornece feedback visual ao usuário e ajuda a indicar que o elemento é interativo.


Integrando os componentes em Login.jsx

Agora reuniremos os componentes estilizados em uma única interface.

O componente Login.jsx será responsável por organizar:

Login
│
├── LabelTitle
│
├── Label + InputText
│
├── Label + InputPassword
│
└── Button

Login.jsx​

import "./Login.css";

import Label from "./Label.jsx";
import LabelTitle from "./LabelTitle.jsx";
import Button from "./Button.jsx";
import InputText from "./InputText.jsx";
import InputPassword from "./InputPassword.jsx";

function Login() {

return (
<div className="login-container">

<form className="login-form">

<LabelTitle texto="Login" />

<div className="form-group">
<Label texto="Usuário:" />
<InputText placeholder="user..." />
</div>

<div className="form-group">
<Label texto="Senha:" />
<InputPassword placeholder="password..." />
</div>

<Button valor="Logar" />

</form>

</div>
);
}

export default Login;

Agora temos três classes importantes:

login-container
login-form
form-group

Cada uma possui uma responsabilidade diferente na organização visual.


Estilizando a tela de Login

Login.css​

.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f5f5f5;
width: 100vmax;
}

.login-form {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
width: 320px;
}

.form-group {
margin-bottom: 1rem;
display: flex;
flex-direction: column;
}

O container principal

A classe:

.login-container

é responsável pela organização geral da tela.

.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f5f5f5;
width: 100vmax;
}

Introdução ao Flexbox

A propriedade:

display: flex;

transforma o elemento em um Flex Container.

Seus elementos filhos passam a ser organizados utilizando o modelo Flexbox.

.login-container
│
│ display: flex
▼
┌───────────────────────────────┐
│ │
│ login-form │
│ │
└───────────────────────────────┘
Flexbox

Flexbox é um modelo de layout CSS utilizado para organizar e alinhar elementos de maneira flexível.

Ele é especialmente útil para:

  • centralização;
  • alinhamento;
  • distribuição de espaço;
  • organização em linhas ou colunas.

Centralização horizontal

justify-content: center;

Centraliza o conteúdo no eixo principal.

Neste caso:

←────────── [ formulário ] ──────────→

Centralização vertical

align-items: center;

Centraliza os elementos no outro eixo.

Combinando:

display: flex;
justify-content: center;
align-items: center;

obtemos:

┌──────────────────────────────────┐
│ │
│ │
│ ┌────────────┐ │
│ │ Login │ │
│ │ │ │
│ │ formulário │ │
│ └────────────┘ │
│ │
│ │
└──────────────────────────────────┘

Unidade vh

Observe:

height: 100vh;

A unidade:

vh = viewport height

representa uma porcentagem da altura visível da janela.

Assim:

100vh

corresponde à altura total da viewport.


Fundo da aplicação

background-color: #f5f5f5;

define um fundo cinza claro para a tela.

Isso ajuda a destacar visualmente o formulário branco.


O formulário

A classe:

.login-form

define a aparência do formulário.

.login-form {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
width: 320px;
}

Visualmente temos:

       Fundo da página
┌──────────────────────────────┐
│ │
│ ┌──────────────────┐ │
│ │ Login │ │
│ │ │ │
│ │ Usuário │ │
│ │ [____________] │ │
│ │ │ │
│ │ Senha │ │
│ │ [____________] │ │
│ │ │ │
│ │ [ Logar ] │ │
│ └──────────────────┘ │
│ │
└──────────────────────────────┘

Esse tipo de apresentação é frequentemente chamado de Card Layout.


Sombra

Observe:

box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);

A sombra ajuda a destacar o formulário do fundo.

Temos:

0px     → deslocamento horizontal
4px → deslocamento vertical
10px → desfoque
rgba() → cor e transparência

Agrupando Label e Input

No Login.jsx, cada campo está dentro de:

<div className="form-group">

Sua estilização é:

.form-group {
margin-bottom: 1rem;
display: flex;
flex-direction: column;
}

A propriedade:

flex-direction: column;

faz com que os elementos sejam organizados verticalmente.

Assim:

form-group
│
├── Label
│
└── Input

é apresentado como:

Label
↓
Input

em vez de:

Label → Input

Isso facilita a leitura e a organização do formulário.


Utilizando o novo Login no App.jsx

Agora precisamos indicar ao componente principal que queremos utilizar a versão estilizada da tela de login.

App.jsx​

// import Login from "./componentes/props/Login.jsx";
// import Login from "./componentes/state/Login.jsx";

import Login from "./componentes/CSS/Login.jsx";

function App() {

return (
<>
<Login />
</>
);
}

export default App;

O componente App continua simples.

Sua responsabilidade é renderizar:

<Login />

A diferença está na versão importada:

import Login from "./componentes/CSS/Login.jsx";

Assim, podemos manter versões diferentes do mesmo exemplo durante o processo de aprendizagem:

componentes/
│
├── props/
│ └── Login.jsx
│
├── state/
│ └── Login.jsx
│
└── CSS/
└── Login.jsx
Evolução do projeto

Essa organização permite observar a evolução da aplicação:

Componentes
│
▼
Props
│
▼
State
│
▼
Eventos
│
▼
CSS
│
▼
Interface estilizada

Visão geral da aplicação

Agora nossa aplicação possui uma combinação de diferentes conceitos:

                         Login.jsx
│
┌────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
Componentes Estados CSS
│ │ │
▼ ▼ ▼
reutilização reatividade aparência
│ │ │
└────────────────┼─────────────────┘
▼
Interface final

Também podemos observar a relação entre cada componente e sua folha de estilo:

Label.jsx ─────────────► Label.css

LabelTitle.jsx ────────► LabelTitle.css

InputText.jsx ─────────► InputText.css

InputPassword.jsx ─────► InputPassword.css

Button.jsx ────────────► Button.css

Login.jsx ─────────────► Login.css

Conceitos-chave

Neste capítulo trabalhamos com:

ConceitoAplicação
CSSDefine a apresentação visual
classNameAssocia classes CSS ao JSX
displayDefine o comportamento de exibição
width e heightControlam dimensões
paddingDefine espaçamento interno
marginDefine espaçamento externo
borderDefine bordas
border-radiusArredonda os cantos
colorDefine a cor do texto
background-colorDefine a cor de fundo
box-shadowAdiciona sombra
:focusEstiliza elementos que possuem foco
:hoverEstiliza elementos sob o ponteiro
transitionSuaviza mudanças de propriedades
FlexboxOrganiza e alinha elementos
flex-directionDefine a direção dos elementos flexíveis
justify-contentControla alinhamento no eixo principal
align-itemsControla alinhamento no eixo transversal
vhUnidade relativa à altura da viewport
remUnidade relativa ao tamanho base da fonte

Exercício de Fixação

Neste capítulo adicionamos estilização CSS aos componentes desenvolvidos anteriormente.

O objetivo deste exercício é implementar, executar, testar e validar todos os códigos apresentados.

Atividade​

Crie a estrutura:

src/
└── componentes/
└── CSS/
├── Login.jsx
├── Login.css
├── Label.jsx
├── Label.css
├── LabelTitle.jsx
├── LabelTitle.css
├── InputText.jsx
├── InputText.css
├── InputPassword.jsx
├── InputPassword.css
├── Button.jsx
└── Button.css

Implemente os códigos apresentados ao longo deste capítulo.

Depois, configure o App.jsx para utilizar:

import Login from "./componentes/CSS/Login.jsx";

Execute a aplicação:

npm run dev

Testando Label​

Verifique se:

<Label texto="Usuário:" />

apresenta corretamente o texto.

Observe se as regras de Label.css foram aplicadas:

.label

Verifique principalmente:

  • tamanho da fonte;
  • cor;
  • espaçamento inferior;
  • comportamento em bloco.

Testando LabelTitle​

Verifique se:

<LabelTitle texto="Login" />

possui maior destaque visual que os demais rótulos.

Compare:

LabelTitle
│
└── título

Label
│
└── rótulo

Testando InputText​

Digite diferentes valores no campo.

Verifique se o comportamento estudado anteriormente continua funcionando:

digitação
│
▼
onChange
│
▼
estado

Em seguida, verifique a estilização.

Clique no campo e observe se o seletor:

input[type="text"]:focus

modifica a borda e adiciona o efeito visual.

Apague o conteúdo e verifique também a mensagem de erro.


Testando InputPassword​

Digite uma senha e verifique se o estado continua sendo atualizado.

Observe também a mudança visual quando o campo recebe foco:

input[type="password"]:focus

Compare o comportamento visual com o InputText.


Testando Button​

Verifique inicialmente:

Logar

Passe o ponteiro sobre o botão e observe o efeito:

.btn:hover

Depois clique no botão.

O texto deverá ser alterado para:

Você clicou!

Assim, podemos observar que a estilização CSS não modifica o funcionamento do estado e dos eventos implementados anteriormente.


Testando o layout​

Observe a interface completa.

Verifique se o formulário está:

  • centralizado horizontalmente;
  • centralizado verticalmente;
  • destacado sobre o fundo;
  • organizado em formato de card;
  • com Label e Input organizados verticalmente;
  • com espaçamento adequado entre os campos.

Identifique no CSS quais propriedades são responsáveis por cada comportamento.


Validação final​

Ao concluir os testes, procure identificar a relação:

Componente JSX
│
├── estrutura
├── props
├── state
├── eventos
│
└── className
│
▼
Arquivo CSS
│
▼
Apresentação visual
Objetivo do exercício

O objetivo não é apenas reproduzir a aparência da aplicação.

Durante os testes, identifique qual regra CSS é responsável por cada alteração visual e observe que o CSS trabalha em conjunto com os conceitos de componentes, props, states e eventos estudados anteriormente.


Resumo

Neste capítulo evoluímos nossa aplicação React adicionando estilos CSS aos componentes.

Mantivemos os conceitos estudados anteriormente:

Componentes
Props
State
Eventos
Inputs controlados
Renderização condicional

e acrescentamos:

CSS
Classes
Seletores
Pseudoclasses
Espaçamentos
Cores
Bordas
Sombras
Flexbox

A aplicação passa, portanto, a combinar:

Estrutura
+
Comportamento
+
Apresentação
=
Interface React

Nos próximos conteúdos, essa separação será importante para construir interfaces cada vez mais organizadas, reutilizáveis e visualmente consistentes.