Skip to content
Versão preliminar. Cores, componentes e layout ainda vão mudar: falta receber o padrão de interface do TJES (template-basico-vue) e o manual de identidade visual.

BaseSegmented ​

Filtro rápido de uma lista: poucas opções lado a lado, cada uma com quantos registros traz. Só uma fica escolhida por vez.

Filtro escolhido: all

Uso ​

  • Use acima de uma tabela ou lista para trocar o recorte com um clique: "Todos", "Em atraso", "Paralisadas".
  • De duas a cinco opções, com rótulos curtos. Com mais opções ou rótulos longos, use uma lista de seleção.
  • A primeira opção costuma ser "Todos", com o total da lista, e vem escolhida ao abrir a tela.
  • A contagem é opcional. Quando existe, diz quantos registros a opção traz com os outros filtros da tela aplicados, e o número muda junto com a busca.
  • Uma opção sem registros pode ficar desabilitada, mas prefira mostrá-la com 0: a pessoa entende que não há nada ali, em vez de achar que o filtro não funciona.
  • O filtro age na hora, sem botão de aplicar. Para escolher uma resposta dentro de um formulário, use o BaseRadioGroup; para navegar entre seções da tela, use abas.

Estados ​

Use disabled enquanto a lista recarrega. As opções continuam no foco do teclado, mas ignoram cliques e setas, para a pessoa não perder o lugar.

Estados de formulário (invalid, warn, readonly) não se aplicam: o filtro não guarda dado e sempre tem uma opção válida. Também não há loading: quem carrega é a lista, que recebe aria-busy enquanto busca.

Tamanhos ​

A altura total segue a dos controles: 32, 40 e 48 px. Use o mesmo tamanho da busca ao lado.

Estilo ​

PropriedadeToken
Trilho--tj-layer-accent-01, contorno interno --tj-border-subtle-02
Opçãotexto --tj-text-helper, peso --tj-weight-medium; hover --tj-text-primary
Opção pressionada--tj-layer-accent-02
Opção escolhidafundo --tj-layer-01, contorno interno de 1 px --tj-border-strong-02, texto --tj-text-primary, peso --tj-weight-semibold, sombra leve
Contagem--tj-layer-accent-02; na opção escolhida --tj-layer-selected-01 e --tj-link-primary-hover
Desabilitadatexto --tj-icon-secondary
Fococontorno sólido de 2 px --tj-border-interactive, por dentro da opção
Fonte--tj-text-sm (--tj-text-xs no pequeno, --tj-text-md no grande)
Raio--tj-radius-md na opção; no trilho, o mesmo raio mais o recuo de 3 px
Altura--tj-control-sm/md/lg (32, 40, 48 px) no trilho

O rótulo reserva a largura do peso semibold, então trocar a opção escolhida não empurra as outras.

Código ​

vue
<script setup lang="ts">
import { computed, ref } from 'vue'
import { BaseSegmented, type SegmentedOption } from '@lab-inovacao/design-system'

interface Contract { id: number; status: 'running' | 'late' | 'stopped' }

// Vem da consulta da tela, já com a busca e os outros filtros aplicados.
const contracts = ref<Contract[]>([])
const countOf = (status: Contract['status']) => contracts.value.filter((contract) => contract.status === status).length

const situation = ref('all')
const options = computed<SegmentedOption[]>(() => [
  { value: 'all', label: 'Todos', count: contracts.value.length, countLabel: 'contratos' },
  { value: 'late', label: 'Em atraso', count: countOf('late'), countLabel: 'contratos' },
  { value: 'stopped', label: 'Paralisadas', count: countOf('stopped'), countLabel: 'contratos' },
])
</script>

<template>
  <BaseSegmented v-model="situation" label="Situação" controls="contracts-table" :options="options" />
  <table id="contracts-table">…</table>
</template>
PropriedadeTipoPadrãoDescrição
optionsSegmentedOption[]—Opções na ordem em que aparecem
labelstring—O que as opções filtram; vira o nome acessível do grupo
v-modelstring—value da opção escolhida
size'sm' | 'md' | 'lg''md'Altura do controle
disabledbooleanfalseIgnora cliques e teclas; as opções seguem focáveis
controlsstring—Opcional. id da tabela ou lista que o filtro muda; vira aria-controls no grupo

SegmentedOption:

CampoTipoDescrição
valuestringValor gravado no v-model; único no grupo
labelstringTexto da opção
countnumberOpcional. Quantos registros a opção traz, num BaseCount
countLabelstringOpcional. O que a contagem conta, como "contratos"; o leitor de tela ouve "Em atraso 5 contratos"
disabledbooleanOpcional. Desabilita só essa opção

Evento: update:modelValue, com o value da opção escolhida.

Acessibilidade ​

Segue o padrão Radio Group do WAI-ARIA APG.

  • Por que radiogroup e não botões aria-pressed. O protótipo usava botões alternáveis num role="group". Esses botões são anunciados como interruptores independentes ("botão alternável, pressionado"), o que esconde que só um fica ativo e sugere que clicar de novo desliga o filtro. Com role="radiogroup" e role="radio", o leitor de tela diz que a escolha é única e a posição ("2 de 3"), e as setas seguem o mesmo modelo do BaseRadioGroup.
  • O grupo é um <div role="radiogroup"> com aria-label vindo de label. Cada opção é um <button type="button" role="radio"> com aria-checked.
  • O nome de cada opção é o rótulo mais a contagem: "Em atraso 5". Com countLabel, ganha a unidade: "Em atraso 5 contratos". Acima de 99 o contador mostra "99+", mas o leitor de tela ouve o número real.
  • Com controls, o grupo aponta com aria-controls para a tabela ou lista que filtra.
  • Teclado:
TeclaAção
TabEntra no grupo pela opção escolhida (sem nenhuma, pela primeira habilitada) e sai dele na próxima vez
→ ↓Move para a próxima opção e a escolhe; da última, volta à primeira
← ↑Move para a opção anterior e a escolhe; da primeira, vai à última
Home EndVai à primeira ou à última opção habilitada e a escolhe
Espaço EnterEscolhe a opção em foco
  • Home, End e Enter vão além do padrão Radio Group do APG e do BaseRadioGroup, como numa barra de ferramentas. Não atrapalham quem usa só as setas e o espaço.

  • Setas com Alt, Ctrl ou Cmd ficam com o navegador: Alt+← continua voltando a página.

  • As setas pulam opções desabilitadas. Opções desabilitadas usam aria-disabled em vez de disabled, para o leitor de tela continuar anunciando-as; o grupo desabilitado também recebe aria-disabled.

  • A opção escolhida não depende só da cor: ganha superfície elevada com contorno interno de 1 px em --tj-border-strong-02 (acima de 3:1 contra o trilho nos temas claro e escuro), sombra e peso semibold.

  • O foco aparece como contorno sólido de 2 px em --tj-border-interactive, por dentro da opção, acima das vizinhas e junto da sombra da opção escolhida. Fica por dentro porque o trilho rola de lado e cortaria um anel externo, e porque o anel translúcido --tj-ring fica abaixo de 3:1 sobre o trilho.

  • Se a contagem muda enquanto a pessoa está na tela, avise com uma região aria-live perto da lista ("5 contratos em atraso"), não no filtro.

  • Sem animação com prefers-reduced-motion. No alto contraste do Windows (forced-colors), trilho, opção escolhida e foco usam as cores do sistema: a escolhida ganha borda Highlight e o foco, contorno CanvasText, para não se confundirem. Toda opção tem a borda, transparente quando não escolhida, então a linha não muda de largura.

  • Testado com axe em todos os estados: com e sem contagem, contagem acima do limite, sem opção escolhida, opção desabilitada (escolhida ou não), todas as opções desabilitadas, grupo desabilitado (com e sem opção escolhida), contagem com unidade, cada tamanho e com foco (numa opção comum, numa opção desabilitada escolhida e no grupo desabilitado). O axe roda sem layout, então o contraste das cores é conferido na documentação renderizada.