Tema
BaseAppShell, BasePage e BasePageAside
A moldura institucional de todo sistema do Tribunal: cabeçalho azul-marinho com o logo, o nome do sistema, a busca e a pessoa conectada; menu lateral com os módulos; a área de conteúdo, de até 1280 px; e, se o sistema quiser, o rodapé. Cada tela entra nela como um BasePage, com o título da tela e o conteúdo. Telas de detalhe e de formulário usam o BasePageAside para a coluna lateral de 360 px.
Nesta página o exemplo fica numa caixa de 560 px de altura; no sistema, a moldura ocupa a janela inteira. Abaixo de 992 px de largura de janela, o menu lateral vira uma faixa horizontal sob o cabeçalho; o cabeçalho continua preso no alto, e a faixa rola com a página.
Na faixa, os links que não cabem ficam para o lado, e a faixa avisa isso:
- A tela abre com a faixa rolada até o item da tela atual, que fica no meio dela ou, perto de uma das pontas, o mais perto do meio que a faixa deixa. Sem isso, num celular de 390 px, o item atual podia ficar fora da tela. A faixa volta a esse item quando a janela muda de largura, como quando o celular gira. Enquanto a pessoa não mexe na faixa, ela também volta ao item quando os links crescem depois de a tela abrir (a quantidade ou o nome de um item que chega da API, ou a fonte Roboto que termina de carregar) e quando aparece a barra de rolagem da página. Depois que a pessoa rola a faixa ou passa por ela com Tab, a faixa fica onde a pessoa deixou até a próxima troca de tela.
- Na borda que esconde links, aparecem um esmaecido e uma seta, que é um botão e rola a faixa para aquele lado. A seta some quando a faixa chega ao fim daquele lado e volta se os links crescerem depois, como quando a quantidade de um item chega da API.
- Cada link tem pelo menos 44 × 44 px, o alvo de toque recomendado para o dedo.
O cabeçalho se ajusta à largura da própria moldura, não à da janela, e nenhum item fica sobre outro a partir de 320 px. Quando falta espaço, o que importa menos sai primeiro:
| Largura da moldura | O que muda no cabeçalho |
|---|---|
| Abaixo de 1200 px | A busca vira um ícone. Clicar nele, chegar nele com Tab ou usar o atalho abre o campo numa linha própria, que fica aberta enquanto houver texto digitado. |
| Abaixo de 992 px | A secretaria sai e a pessoa aparece só pelo avatar. |
| Abaixo de 576 px | Com logo, o nome do sistema sai e a seta do painel da pessoa também. |
Em qualquer largura, se ainda faltar espaço, a secretaria e depois o nome do sistema terminam em reticências, e o logo diminui por último.
Uso
- Use um
BaseAppShellpor sistema, na raiz do app. Cada tela é umBasePagedentro dele: otitlevira o únicoh1da tela, osubtitlediz o estado da página e o slotactionsrecebe as ações da tela (no máximo um botão primário). - O menu lateral vem da propriedade
nav: grupos com um título curto (Obras,Secretaria) e itens comlabel,hrefeicon. Dê ícone a todos os itens: com o menu recolhido, só os ícones aparecem. - Marque a tela aberta com
current: true. Ela ganha fundo, peso e uma barra na borda esquerda do próprio item, não só cor. - Dê a cada item o ícone do seu assunto, da página Ícones. No menu recolhido só os ícones aparecem, então dois itens com o mesmo ícone ficam iguais. Um nome longo quebra em duas linhas no menu aberto, sem reticências.
- Use
countpara o que pede atenção, como medições abertas, ecountLabelpara dizer o que é contado: o leitor de tela ouve "7 medições abertas" junto com o nome do item. - Com roteador, ouça
navigate, chameevent.preventDefault()e navegue peloitem.href. Onavigatesó chega no clique simples: com Ctrl, Cmd, Shift, Alt ou o botão do meio, o navegador abre o link em outra aba ou janela, como noRouterLink. Sem roteador, os links funcionam sozinhos. - O botão de menu no começo do cabeçalho recolhe o menu lateral para uma faixa só de ícones. Ligue
v-model:collapsedpara guardar a preferência da pessoa. - O slot
searchrecebe a busca geral do sistema: umBaseSearchcomlandmarke, se for a única busca com atalho da tela,shortcut. Ele ganha as cores do cabeçalho. Buscas de lista ficam na tela, não no cabeçalho. - O slot
toolsrecebe botões de ícone, como avisos e ajuda:BaseButtoncomkind="ghost"eicon-only. user-nameeuser-rolemostram quem está conectado. Com o slotuser-menu, o nome vira um botão que abre um painel com links e botões (perfil, sair). Para a ação, use oBaseUserMenuItem, que já vem no estilo do painel. Para a escolha do tema, ponha oBaseThemeSwitchantes do "Sair": escolher um tema não fecha o painel.- O slot
logorecebe oBaseLogocombackground="dark", o logo oficial com o endosso em branco, sempre sobre o azul-marinho. Comhome-href, logo e nome do sistema levam à tela inicial. - Sem
nav, não há menu lateral nem botão de menu: serve para telas de entrada e de erro. - Passe a trilha da tela aberta em
breadcrumb, a partir do roteador, e uma lista vazia nas telas sem trilha. OBasePagemostra a trilha numa linha reservada acima do título, então o título fica na mesma altura em todas as telas. Não ponha oBaseBreadcrumbsolto antes doBasePage: o título desce e a tela pula ao trocar de página. O passo a passo está no Modelo de página. - O cabeçalho fica preso no alto da janela enquanto a página rola, em qualquer largura. No celular ele tem uma linha só, de 56 px; a faixa de ambiente do sistema é a borda de baixo dele e acompanha.
- O menu lateral ocupa a altura toda da moldura, por mais longa que seja a tela; o conteúdo dele fica preso sob o cabeçalho e rola sozinho quando não cabe na janela. O menu nunca deixa a página mais alta: numa tela curta, os links que não cabem rolam dentro do menu.
- O slot
footerrecebe oBaseFooter. Ele ocupa a largura toda, embaixo do menu lateral e do conteúdo, fora do<main>, fica no fim do conteúdo e vai para o fim da janela quando a tela tem pouco conteúdo. Ele não é fixo: rola com a página e nunca cobre o conteúdo. No computador, o texto do rodapé começa na mesma linha do conteúdo da tela. Sem o slot, não há rodapé.
Anatomia
- Link "Pular para o conteúdo": aparece só no primeiro Tab.
- Botão de menu: recolhe e abre o menu lateral; só existe com
nav. - Logo e nome: o slot
logo, oproducte ounit; comhome-href, levam à tela inicial. - Busca: o slot
search. - Ferramentas: o slot
tools, com botões de ícone. - Pessoa conectada: avatar,
user-nameeuser-role; com o slotuser-menu, abre um painel. - Menu lateral: os grupos e itens de
nav, com a quantidade decount, e o slotnav-footerno fim. - Conteúdo: o
BasePage, com a linha da trilha (breadcrumb), o título (title), o slotstatus, osubtitle, o slotactionse o conteúdo; oBasePageAsideacrescenta a coluna lateral pelo slotaside. - Rodapé: o slot
footer, com oBaseFooter, embaixo do conteúdo.
Menu recolhido
Com rodapé
Conteúdo com coluna lateral
O BasePageAside põe a coluna lateral de 360 px à direita do conteúdo quando a área de conteúdo tem pelo menos 1080 px. Abaixo disso, a coluna vai para baixo do conteúdo. A medida é a da área de conteúdo, não a da janela: com o menu recolhido, as duas colunas aparecem em telas menores. Nesta página, estreita, elas aparecem empilhadas.
Itens da medição
Planilha da medição 29.Resumo
Valor medido e saldo do contrato.Histórico
Envios e devoluções.Use para detalhe com decisão (resumo, histórico e barra de decisão na lateral) e formulário em etapas (o andamento na lateral). Na lateral, ponha o que apoia a decisão, nunca o único caminho para concluir a tarefa: no celular ela fica no fim da página. A barra de decisão vai no topo da lateral: a coluna rola com a página, então o que fica embaixo só aparece quando a pessoa chega ao fim do conteúdo.
Faça e não faça
| Faça | Não faça |
|---|---|
Um BaseAppShell na raiz do sistema de obras, com cada tela num BasePage. | Montar uma moldura nova em cada tela. |
| Dar ícone a todos os itens do menu: recolhido, só eles aparecem. | Deixar itens sem ícone, que viram espaços vazios no menu recolhido. |
| Pôr no cabeçalho a busca geral do sistema. | Pôr no cabeçalho a busca da lista de contratos; ela fica na tela. |
Estilo
| Propriedade | Token |
|---|---|
| Fundo do cabeçalho | --tj-background-brand, igual nos dois temas |
| Texto do cabeçalho | --tj-text-on-brand; secundário --tj-text-on-brand-secondary |
| Busca e botões no cabeçalho | fundo --tj-layer-on-brand, borda --tj-border-on-brand |
| Altura do cabeçalho | --tj-header (64 px; 56 px abaixo de 992 px) |
| Menu lateral | fundo --tj-layer-01, borda --tj-border-subtle-02, largura --tj-sidebar (272 px; 64 px recolhido) |
| Item do menu | texto --tj-text-secondary, hover --tj-layer-hover-01 |
| Item atual | fundo --tj-layer-selected-01, texto --tj-text-brand, barra de --tj-space-1 em --tj-border-interactive dentro do item |
| Ícone do item | --tj-icon-sm (16 px), cor --tj-icon-secondary; --tj-icon-interactive no item atual |
| Botão do menu e pessoa | botão do menu em --tj-text-on-brand com ícone de 20 px; a pessoa com menu tem contorno --tj-border-on-brand e a seta para baixo |
| Dica e ponto do menu recolhido | dica com fundo --tj-background-inverse e texto --tj-text-inverse; ponto --tj-icon-interactive |
| Rodapé | os tokens do BaseFooter |
| Área de conteúdo | fundo --tj-background, largura máxima --tj-content-max (1280 px), margem --tj-space-8 (--tj-space-4 no celular) |
| Linha da trilha | altura mínima --tj-space-6, reservada mesmo vazia; --tj-space-2 até o título |
| Blocos da tela | um embaixo do outro, a --tj-page-gap (24 px) |
| Painel da pessoa | fundo --tj-layer-01, sombra --tj-shadow-2 |
| Coluna lateral | 360 px, intervalo --tj-page-gap |
| Foco no cabeçalho | contorno sólido de 2 px em --tj-text-on-brand, além do --tj-ring |
Código
vue
<BaseAppShell
v-model:collapsed="menuCollapsed"
product="Gestão de Obras"
unit="Secretaria de Engenharia"
home-href="/"
:nav="menu"
user-name="Marta Coelho"
user-role="Fiscal de contrato"
:breadcrumb="breadcrumbOf(route)"
@navigate="(item, event) => { event.preventDefault(); router.push(item.href) }"
>
<template #logo><BaseLogo background="dark" /></template>
<template #search><BaseSearch v-model="query" label="Buscar em todo o sistema" landmark shortcut /></template>
<template #user-menu>
<a href="/perfil">Meu perfil</a>
<BaseUserMenuItem icon="box-arrow-right" @click="signOut">Sair</BaseUserMenuItem>
</template>
<BasePage title="Contrato 012/2026" subtitle="Reforma do Fórum de Alegre">
<template #status><BaseBadge tone="success">Em execução</BaseBadge></template>
<template #actions><BaseButton kind="secondary">Editar contrato</BaseButton></template>
<BasePageAside>
<BaseCard title="Medições">…</BaseCard>
<template #aside><BaseCard title="Resumo">…</BaseCard></template>
</BasePageAside>
</BasePage>
</BaseAppShell>Propriedades
BaseAppShell
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
product | string | obrigatória | Nome do sistema ao lado do logo |
unit | string | — | Segunda linha, como a secretaria; some abaixo de 992 px |
homeHref | string | — | Faz do logo e do nome um link para a tela inicial |
nav | readonly NavGroup[] | [] | Grupos do menu lateral; sem ele não há menu |
navLabel | string | 'Menu principal' | Nome acessível do menu lateral |
userName | string | — | Pessoa conectada, com avatar |
userRole | string | — | Papel da pessoa, como "Fiscal de contrato" |
breadcrumb | readonly BreadcrumbItem[] | — | Trilha da tela aberta, que o BasePage mostra acima do título; uma lista vazia mantém a linha reservada |
v-model:collapsed | boolean | false | Menu recolhido em faixa de ícones (computador) |
NavGroup: { label?: string, items: NavItem[] }. NavItem: { label: string, href: string, icon?: string, count?: number, countLabel?: string, current?: boolean }.
Slots: default (as telas), logo, search, tools, user-menu (links e botões do painel da pessoa; um <hr> separa grupos), nav-footer (nota no pé do menu lateral, escondida quando recolhido e no celular) e footer (o BaseFooter, embaixo do conteúdo e fora do <main>). Eventos: navigate, com o item e o evento do clique, e update:collapsed.
BasePage
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
title | string | obrigatória | Título da tela, o único h1 |
subtitle | string | — | Linha abaixo do título com o estado da página |
Slots: default (os blocos da tela, empilhados a --tj-page-gap), actions (botões da tela, à direita do título; ocupam a largura toda no celular), status (o selo da situação do registro, ao lado do título) e breadcrumb (troca a trilha que vem do BaseAppShell; com ele a linha também fica reservada). Fora de um BaseAppShell, o BasePage desenha o próprio <main> num contêiner do Bootstrap, para apps que ainda não usam a moldura.
BasePageAside
Sem propriedades.
Slots: default (conteúdo principal) e aside (coluna lateral de 360 px). Sem o slot aside, só o conteúdo aparece.
Acessibilidade
- Segue o padrão Landmarks do WAI-ARIA APG: o cabeçalho é
<header>(banner), o menu lateral é<nav>com o nome denavLabel, o conteúdo é o único<main>e oBaseFooterdo slotfooteré o<footer>(contentinfo), fora do<main>. Dentro da moldura, oBasePagenão cria outro<main>. - Teclado:
| Tecla | O que faz |
|---|---|
| Tab | O primeiro mostra "Pular para o conteúdo"; depois passa pelo cabeçalho (menu, logo, busca, ferramentas, pessoa), pelo menu lateral, pelo conteúdo e pelos links do rodapé |
| Enter | No link de pular, leva o foco ao conteúdo; num link do menu, abre a tela; num botão, aciona |
| Espaço | Aciona o botão de menu e o botão da pessoa |
| Esc | Fecha o painel da pessoa e devolve o foco ao botão; no menu recolhido, esconde a dica do item sem mover o foco |
- O primeiro Tab da tela mostra o link "Pular para o conteúdo". Ele leva o foco ao
<main>sem mudar o endereço, para não confundir roteadores com#. - Na faixa do celular e do tablet, a seta de cada borda é um botão: "Ver links anteriores" no começo e "Ver mais links" no fim. Com clique, toque, Enter ou Espaço, ela rola a faixa para aquele lado, e o clique nunca passa para o link de baixo. Quando a faixa chega ao fim daquele lado, a seta some e o foco vai para o link da ponta. O esmaecido é só visual. Com Tab, o link em foco que estava cortado pela borda ou coberto pela seta vai para o meio da faixa. No alto contraste do Windows, o esmaecido some e a seta fica sobre o fundo do sistema, com uma linha do lado de dentro.
- O cabeçalho fica fixo no topo, no computador e no celular. A página reserva a altura dele (
scroll-padding-top), então o item focado, o destino de uma âncora e o começo do conteúdo depois do link de pular nunca ficam escondidos sob o cabeçalho. - Cada grupo do menu é uma lista com o nome do seu título. A tela aberta tem
aria-current="page", e o leitor de tela anuncia "página atual". - O botão de menu tem nome fixo, "Menu lateral", e diz o estado por
aria-expanded;aria-controlsaponta para o menu. "Recolhido" quer dizer só ícones: os links continuam todos na ordem de leitura e de Tab. - Recolhido, cada item mantém o nome e a quantidade para o leitor de tela. O nome aparece numa dica ao lado do ícone, tanto com o mouse quanto com o foco do teclado, e Esc esconde a dica sem mover o foco. Um ponto sobre o ícone avisa que o item tem quantidade acima de zero.
- O painel da pessoa segue o padrão Disclosure: o botão diz
aria-expanded, os itens são links e botões comuns, alcançados com Tab. Esc fecha e devolve o foco ao botão; clicar fora, escolher um item ou sair do painel com Tab também fecha. Ao escolher um item, o foco volta ao botão, a não ser que o próprio item o tenha levado a outro lugar, como um modal. Ele não é umrole="menu", então as setas não são prometidas. A exceção é oBaseThemeSwitch, um grupo de rádio dentro do painel: o Tab entra nele pela opção escolhida, as setas trocam de opção só dentro dele, e escolher um tema não fecha o painel. - Em molduras estreitas, o nome da pessoa e o nome do sistema saem da tela, mas continuam para o leitor de tela no botão da pessoa e no bloco do logo (um link, com
home-href). A busca recolhida continua sendo o mesmo campo, com o mesmo nome, na mesma ordem de Tab. - O foco no cabeçalho ganha contorno branco sólido, porque o anel translúcido não chega a 3:1 sobre o azul-marinho.
- As transições do menu recolhido, do link de pular, da seta do painel e das bordas da faixa somem com
prefers-reduced-motion. Em alto contraste do Windows, o item atual ganha contorno e o cabeçalho, borda; o conteúdo não ganha contorno quando recebe o foco do link de pular. - Os testes passam o axe em jsdom com o menu aberto, sem menu, com o painel da pessoa aberto e com página e coluna lateral, e rodam as regras de página inteira (
region,bypass, marcos únicos) com a moldura completa. O jsdom não aplica CSS, então o menu recolhido, o contraste e as quebras de layout são conferidos nas páginas desta documentação. - Nesta documentação, os exemplos ficam dentro do
<main>da própria página, então aparecem com um<main>aninhado e um cabeçalho que não é o banner. Os marcos foram conferidos nos testes, não aqui.