Vitral 0.2
Referência

Acessibilidade

Uma condição para um componente existir, não um marco no caminho até lá.

Um componente não está pronto enquanto a sua acessibilidade não funciona: o role, um nome acessível, cada estado e valor que se aplica, as relações que o ligam ao seu rótulo, à sua descrição e ao seu popup, e o comportamento de teclado do padrão WAI-ARIA APG correspondente.

O que todo teste faz

  • Roda o axe sobre os estados representativos e, num overlay, com ele fechado e aberto.
  • Conduz o teclado pelas teclas do padrão e verifica o que mudou.
  • Verifica os atributos ARIA, não só as classes: um teste que só vê classes não distingue uma div estilizada de um controle.
Select.spec.ts
it('is a combobox that answers the keyboard', async () => {
    const wrapper = mount(Select, { props: { options, optionLabel: 'name' } });

    await expectNoA11yViolations(wrapper);     // axe, closed
    await press(wrapper, 'ArrowDown');         // opens and marks the first option
    await expectNoA11yViolations(wrapper);     // axe, open

    expect(trigger.attributes('aria-expanded')).toBe('true');
    expect(trigger.attributes('aria-activedescendant')).toBe(optionId(0));
});

O que isso significa na prática

  • Os campos são controles de verdade: <label for>, name e aria-describedby chegam até eles sem props extras, porque os atributos que não são de classe passam para o controle, e não para o wrapper.
  • Os overlays prendem o foco quando são modais, devolvem-no ao fechar e fecham com Escape ou com um clique fora. Só o de cima, então um diálogo sobre outro diálogo se comporta.
  • As listas aceitam type-ahead, Home/End e aria-activedescendant, em vez de mover o foco dentro de um popup.
  • Toolbars, listas de abas e grupos de rádio são uma única parada de tabulação, com movimento pelas setas, como o APG pede.
  • Tudo o que é animado para sob prefers-reduced-motion: reduce.
  • Os toasts são live regions (polite, ou um alert para perigo), e um life fica em pausa enquanto o cartão está sob o ponteiro ou em foco.

Contraste, e a única chave de que ele precisa

Texto, anéis de foco e estados desabilitados atendem aos critérios 1.4.3 e 1.4.11 em todos os presets e nos dois esquemas de cores, e um teste mede isso em vez de confiar no olho: o tema é compilado, cada cadeia de var() é seguida até uma cor real, as superfícies translúcidas são compostas sobre o que está atrás delas, e a razão é calculada. Um preset que escorrega para baixo da linha quebra o build.

Uma coisa, de propósito, não vem ligada por padrão. O 1.4.11 também pede que a borda de um campo chegue a 3:1 contra o campo, e estes presets são desenhados com bordas mais discretas que isso — uma linha que sugere onde o controle está em vez de contorná-lo. Os dois conjuntos vêm na folha de estilos; borders: 'strong' passa para os que atendem ao critério e, como é uma marca no <html> e não uma recompilação, pode ser trocado em tempo de execução.

Exemplo
app.use(Vitral, {
    theme: { preset: Ink, borders: 'strong' }
});

// or at runtime, from anywhere:
const { setBorders } = useTheme();
setBorders('strong');

O menu de tema no topo desta página tem a mesma chave, então todos os exemplos do site podem ser lidos dos dois jeitos.

O modo sem estilo não muda nada disso

O modo sem estilo descarta as classes e mantém cada linha acima. Essa é a divisão sobre a qual a biblioteca foi construída: comportamento e semântica são o componente; a aparência é um tema.