🧭 Navigation Tabs animadas com React + Framer Motion
Nível 3 · 🐷 Circe · ⏱️ ~20 min · Pré-requisitos: Node.js (com npm) em uma versão aceita pelo Vite (confira a versão exigida em https://vite.dev/guide/), um terminal e noções básicas de React
> ⚠️ Limite do material: o post do @code.xr traz só a legenda. O código-fonte é entregue por comentário ("Code") e não aparece na página. Por isso o tutorial abaixo é uma versão minha, mínima, do efeito descrito (troca suave de abas com uma "pílula" que desliza). Não é o código original do autor.
📌 Pra que serve
O "Navigation Tabs V2" é um componente de abas com animação fluida e microinterações. Ele usa React, CSS e Framer Motion, e a legenda diz que é responsivo. Serve para dar um acabamento mais profissional a menus, dashboards e páginas de produto. Quem faz front-end se beneficia mais, mas ele também ajuda quem está aprendendo animação em interface.🛠️ Passo a passo
1. Crie um projeto React com Vite (comandos em "Execução rápida"). Se o terminal fizer perguntas, aceite as opções padrão ou escolha React com JavaScript. 2. Instale o Framer Motion comnpm install framer-motion (já incluído nos comandos de "Execução rápida").
3. Crie o componente de abas em src/App.jsx (o código completo está em "Execução rápida"). Cada aba é um botão. A aba ativa renderiza um <motion.span> de fundo (a pílula) com a prop layoutId.
4. Entenda o truque: o Framer Motion vê o mesmo layoutId desaparecer de uma aba e aparecer em outra. Ele anima a transição de posição e tamanho entre as duas. É isso que dá o deslize suave, sem calcular coordenadas na mão.
5. Ajuste a física em transition={{ type: "spring", stiffness: 400, damping: 30 }}. Mais stiffness deixa a pílula mais rápida. Menos damping faz ela "quicar" mais.
6. Estilize em src/App.css: container arredondado escuro, botões com position: relative e a pílula com position: absolute; inset: 0. O texto fica acima dela co
m z-index.
7. Rode com npm run dev e abra o endereço que o Vite mostrar, normalmente http://localhost:5173. Ao clicar nas abas, a pílula desliza de uma para outra.
8. Microinterações do post: o código de "Execução rápida" já usa <motion.button> com whileHover={{ scale: 1.05 }} e whileTap={{ scale: 0.95 }}. Se você montou o componente com <button> comum, troque por <motion.button> e acrescente essas duas props.💡 Dica: para ter conteúdo animado por aba, envolva o painel com <AnimatePresence mode="wait"> (importado de framer-motion) e use initial, animate e exit com opacidade e deslocamento em y. Para o menu caber no celular, use overflow-x: auto no container (o CSS de "Execução rápida" já faz isso).
🔗 Links e instalação
- Fonte: https://www.instagram.com/reel/Dc3chX6zlR6/?stkn=aHR0MGFrd2JjZHFz
- Framer Motion (docs): https://motion.dev
- Vite: https://vite.dev
- React: https://react.dev
npm create vite@latest tabs -- --template react
cd tabs
npm install
npm install framer-motion⚡ Execução rápida
npm create vite@latest tabs -- --template react
cd tabs
npm install
npm install framer-motioncat > src/App.jsx <<'EOF'
import { useState } from "react";
import { motion } from "framer-motion";
import "./App.css";
const tabs = ["Início", "Projetos", "Sobre", "Contato"];
export default function App() {
const [active, setActive] = useState(tabs[0]);
return (
<div className="tabs">
{tabs.map((t) => (
<motion.button
key={t}
className={tab ${active === t ? "active" : ""}}
onClick={() => setActive(t)}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
{active === t && (
<motion.span
layoutId="pill"
className="pill"
transition={{ type: "spring", stiffness: 400, damping: 30 }}
/>
)}
<span className="label">{t}</span>
</
motion.button>
))}
</div>
);
}
EOF
cat > src/App.css <<'EOF'
.tabs { display: flex; gap: 4px; padding: 6px; background: #111; border-radius: 999px; width: fit-content; max-width: 100%; overflow-x: auto; }
.tab { position: relative; border: 0; background: none; color: #aaa; padding: 8px 18px; cursor: pointer; border-radius: 999px; font-size: 1rem; white-space: nowrap; }
.tab.active { color: #fff; }
.pill { position: absolute; inset: 0; background: #6d5dfc; border-radius: 999px; }
.label { position: relative; z-index: 1; }
EOF
npm run dev