Breadcrumbs acessíveis com Claude Code
Implemente breadcrumbs com Claude Code, JSON-LD, aria-current, CSS mobile e testes em React/Astro.
Breadcrumbs parecem uma pequena linha acima do título, mas em produção eles conectam arquitetura do site, links internos, acessibilidade, dados estruturados e layout mobile. Uma versão fraca pode parecer correta e ainda assim não informar a página atual a leitores de tela, gerar JSON-LD com URL relativa ou quebrar em telas pequenas.
Nos templates do ClaudeCodeLab, o primeiro rascunho do Claude Code criou Home > Blog > Title, mas esqueceu aria-current, usou URLs relativas no JSON-LD, quebrou no mobile e exibiu slugs crus. A solução é dar ao Claude Code o contrato completo antes de pedir código.
Este guia mostra como implementar e revisar breadcrumbs acessíveis para React, sites estilo Next.js e Astro. Leia também SEO com Claude Code, acessibilidade, Astro e React.
Contrato de implementação
Breadcrumb não é substituto do botão voltar. Ele mostra hierarquia, leva a páginas superiores e ajuda buscadores a entender relações. O Breadcrumb Pattern do WAI-ARIA APG recomenda uma região de navegação com rótulo e aria-current="page" para a página atual.
Os dados estruturados devem seguir schema.org BreadcrumbList, com position para preservar a ordem. Para Google Search, use a documentação oficial de breadcrumb structured data.
| Decisão | O que definir | Falha comum |
|---|---|---|
| Rótulos | Títulos, categorias ou dicionário | Slugs aparecem sem tratamento |
| URLs | Links HTML e URLs absolutas no JSON-LD | Caminhos relativos nos dados estruturados |
| Página atual | Link ou texto no último item | Estado indicado só por cor |
| Mobile | Mostrar tudo ou ocultar o meio | Breadcrumb ocupa várias linhas |
| Idiomas | Prefixo e labels locais | Mistura de idiomas |
Prompt para Claude Code
Implemente breadcrumbs para um site React/Next.js ou Astro.
Requisitos:
- Receber items como { label: string; href: string }[].
- Adicionar aria-current="page" ao último item.
- Usar nav aria-label="Breadcrumb".
- Marcar separadores com aria-hidden="true".
- Gerar JSON-LD BreadcrumbList a partir do mesmo array items.
- Converter URLs do JSON-LD em absolutas usando siteUrl.
- Criar helper para montar items a partir de pathname.
- Transformar slugs em texto legível e permitir override por dicionário.
- No mobile, ocultar itens intermediários mantendo a página atual legível.
- Adicionar testes Vitest para raiz, caminhos profundos, labels localizados e query strings.
- Ao final, listar checks de acessibilidade e dados estruturados.
Para mudanças maiores, peça uma segunda revisão com a checklist de review.
Componente React
import type { ReactNode } from "react";
export type BreadcrumbItem = {
label: string;
href: string;
};
type BreadcrumbProps = {
items: BreadcrumbItem[];
siteUrl: string;
ariaLabel?: string;
};
function toAbsoluteUrl(siteUrl: string, href: string) {
return new URL(href, siteUrl).toString();
}
function Separator(): ReactNode {
return (
<span className="breadcrumb__separator" aria-hidden="true">
/
</span>
);
}
export function Breadcrumb({
items,
siteUrl,
ariaLabel = "Breadcrumb",
}: BreadcrumbProps) {
if (items.length <= 1) return null;
const jsonLd = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: items.map((item, index) => ({
"@type": "ListItem",
position: index + 1,
item: {
"@id": toAbsoluteUrl(siteUrl, item.href),
name: item.label,
},
})),
};
return (
<>
<nav className="breadcrumb" aria-label={ariaLabel}>
<ol className="breadcrumb__list">
{items.map((item, index) => {
const isCurrent = index === items.length - 1;
return (
<li className="breadcrumb__item" key={item.href}>
{index > 0 ? <Separator /> : null}
{isCurrent ? (
<span className="breadcrumb__current" aria-current="page">
{item.label}
</span>
) : (
<a className="breadcrumb__link" href={item.href}>
{item.label}
</a>
)}
</li>
);
})}
</ol>
</nav>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
</>
);
}
Helper de rotas
import type { BreadcrumbItem } from "@/components/Breadcrumb";
export type BreadcrumbLabels = Record<string, string>;
function titleize(segment: string) {
return decodeURIComponent(segment)
.replace(/[-_]+/g, " ")
.replace(/\b\w/g, (char) => char.toUpperCase());
}
export function buildBreadcrumbs(
pathname: string,
labels: BreadcrumbLabels = {},
): BreadcrumbItem[] {
const cleanPath = pathname.split(/[?#]/)[0].replace(/\/+$/, "") || "/";
const segments = cleanPath.split("/").filter(Boolean);
const items: BreadcrumbItem[] = [
{ label: labels["/"] ?? "Home", href: "/" },
];
let href = "";
for (const segment of segments) {
href += `/${segment}`;
items.push({
label: labels[href] ?? labels[segment] ?? titleize(segment),
href,
});
}
return items;
}
import { Breadcrumb } from "@/components/Breadcrumb";
import { buildBreadcrumbs } from "@/lib/breadcrumbs";
const siteUrl = "https://claudecodelab.com";
export default async function ArticlePage() {
const pathname = "/pt/blog/claude-code-breadcrumb-navigation";
const labels = {
"/": "Início",
"/pt": "Português",
"/pt/blog": "Artigos",
"/pt/blog/claude-code-breadcrumb-navigation":
"Breadcrumbs acessíveis com Claude Code",
};
const items = buildBreadcrumbs(pathname, labels);
return (
<main>
<Breadcrumb items={items} siteUrl={siteUrl} ariaLabel="Breadcrumb" />
<h1>Breadcrumbs acessíveis com Claude Code</h1>
</main>
);
}
No Astro, mantenha o mesmo formato de items, use Astro.url.pathname e emita JSON-LD com set:html={JSON.stringify(jsonLd)}.
CSS mobile
.breadcrumb { margin-block: 0 1rem; font-size: 0.875rem; color: #4b5563; }
.breadcrumb__list { display: flex; flex-wrap: wrap; gap: 0.25rem; list-style: none; margin: 0; padding: 0; }
.breadcrumb__item { align-items: center; display: inline-flex; min-width: 0; }
.breadcrumb__link { color: #2563eb; text-decoration: underline; text-underline-offset: 0.15em; }
.breadcrumb__current { color: #111827; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.breadcrumb__separator { color: #9ca3af; margin-inline: 0.35rem; }
@media (max-width: 640px) {
.breadcrumb__list { flex-wrap: nowrap; }
.breadcrumb__item:not(:first-child):not(:nth-last-child(-n + 2)) { display: none; }
.breadcrumb__item:nth-last-child(2)::after { color: #9ca3af; content: "..."; margin-inline: 0.35rem; }
.breadcrumb__current { max-width: 58vw; }
}
Testes
import { describe, expect, it } from "vitest";
import { buildBreadcrumbs } from "./breadcrumbs";
describe("buildBreadcrumbs", () => {
it("returns only Home for the root path", () => {
expect(buildBreadcrumbs("/")).toEqual([{ label: "Home", href: "/" }]);
});
it("builds nested breadcrumbs and ignores query strings", () => {
expect(buildBreadcrumbs("/blog/claude-code?page=2")).toEqual([
{ label: "Home", href: "/" },
{ label: "Blog", href: "/blog" },
{ label: "Claude Code", href: "/blog/claude-code" },
]);
});
it("uses localized labels when provided", () => {
expect(
buildBreadcrumbs("/pt/blog/claude-code-breadcrumb-navigation", {
"/": "Início",
"/pt": "Português",
"/pt/blog": "Artigos",
"/pt/blog/claude-code-breadcrumb-navigation": "Breadcrumbs acessíveis",
}),
).toEqual([
{ label: "Início", href: "/" },
{ label: "Português", href: "/pt" },
{ label: "Artigos", href: "/pt/blog" },
{ label: "Breadcrumbs acessíveis", href: "/pt/blog/claude-code-breadcrumb-navigation" },
]);
});
});
Em E2E, confira nav[aria-label], um único aria-current="page", JSON-LD válido, URLs absolutas e layout mobile sem sobreposição. Veja também testes com Playwright.
Casos de uso e armadilhas
Use breadcrumbs em blogs e documentação, páginas de treinamento ou ecommerce, telas SaaS profundas e sites multilíngues. Eles melhoram contexto, navegação e links internos.
As falhas comuns são indicar a página atual só por cor, usar URL relativa no JSON-LD, mostrar slug cru, quebrar no mobile e manter duas fontes de dados para UI e estrutura. Antes de publicar, revise aria-label, aria-current, separadores ocultos, BreadcrumbList, position, URL absoluta, consistência UI/JSON-LD, mobile, idioma e validação no Search Console.
CTA e verificação
No ClaudeCodeLab, breadcrumbs ajudam a levar leitores para a folha gratuita, produtos e treinamento/consultoria Claude Code. Verifiquei os exemplos separando componente React, helper e testes Vitest; gerar UI e JSON-LD do mesmo items eliminou divergências após renomear categorias.
Resumo
Breadcrumb acessível não é só um separador entre links. Peça a Claude Code labels, rotas, aria-current, JSON-LD, URLs absolutas, comportamento mobile e testes, e revise com as fontes oficiais antes de publicar.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Como pedir ao Claude Code para mexer em um único arquivo
Do desastre em que um 'deixa melhor' alterou 40 linhas nasceu um template de prompt que limita o escopo, valida e permite reverter.
Recuperar de negações de permissão no Claude Code sem enfraquecer guardrails
Transforme um comando negado em plano seguro com motivo, alternativa, provas e critérios de nova tentativa.
Claude Code Harness Smoke Test: prova de 15 minutos antes de confiar em um agente
Um smoke test para escopo, áreas bloqueadas, comandos de prova, URL pública e CTAs de receita no Claude Code.