Ir para o conteúdo principal
WBC - GovBR-DS
Copiar página como Markdown para IA

Integração com Formulários e Validadores

Os Web Components do GovBR-DS encapsulam a estrutura e a aparência dos inputs em um Shadow DOM, o que altera levemente a forma como as bibliotecas de validação interagem com eles.

Esta página concentra o contrato geral de validação, a matriz de componentes e as receitas de integração com React, Angular e Vue. Os detalhes de cada campo ficam na seção Validação da página do componente correspondente.

Para facilitar a integração, nossa documentação sugere algumas "receitas" para cenários práticos nos principais frameworks suportados.

A diferença fundamental

Os controles emitem input e change no host e mantêm o valor em propriedades como value, checked e files. Os wrappers oficiais traduzem esse contrato para ControlValueAccessor, props React e v-model. Consulte Eventos nativos e eventos customizados para a semântica e a migração dos aliases da série 2.x.


🔵 Integração com React Hook Form

Com os wrappers nativos exportados do pacote @govbr-ds/webcomponents-react, componentes de entrada como br-input repassam a referência (ref) do HTMLElement e os eventos canônicos onInput e onChange. Para ligar ao ecossistema do React Hook Form sem fricção, a estratégia ideal é o uso do Controller.

Exemplo de uso com `Controller`

import { useForm, Controller } from 'react-hook-form';
import { br-input, br-button } from '@govbr-ds/webcomponents-react';

export default function MyForm() {
const { control, handleSubmit } = useForm({
defaultValues: {
username: ''
}
});

const onSubmit = data => console.log(data);

return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="username"
control={control}
rules={{ required: "O nome de usuário é obrigatório" }}
render={({ field: { onChange, onBlur, value }, fieldState: { error } }) => (
<br-input
label="Nome de usuário"
value={value}
required
onInput={(event) => onChange(event.currentTarget.value)}
onBlur={onBlur}
state={error ? 'danger' : 'info'}
/>
)}
/>
<br-button type="submit">Enviar</br-button>
</form>
);
}

valueChanges equivalente, refs e eventos customizados

No React, alterações externas devem passar por state/props. Eventos DOM nativos usam event.currentTarget.value; eventos CustomEvent usam event.detail:

import { useEffect, useRef, useState } from 'react';
import { BrSelect } from '@govbr-ds/webcomponents-react';

export function SelectField() {
const selectRef = useRef<HTMLBrSelectElement>(null);
const [value, setValue] = useState('');
const [validationMessage, setValidationMessage] = useState('');

useEffect(() => {
const select = selectRef.current;
if (!select) return;
const onValidation = (event: CustomEvent<{ message: string | null }>) =>
setValidationMessage(event.detail.message ?? '');
select.addEventListener('brSelectValidationChange', onValidation);
return () => select.removeEventListener('brSelectValidationChange', onValidation);
}, []);

return (
<BrSelect
ref={selectRef}
value={value}
options={[{ label: 'Brasília', value: 'df' }]}
onChange={(event) => setValue(event.currentTarget.value as string)}
/>
);
}

🔴 Integração com Angular Reactive Forms

No pacote @govbr-ds/webcomponents-angular, nós providenciamos os Value Accessors nativos. Isso garante que as diretivas formControlName e [formControl] funcionem sem esforço extra, de forma similar ao que você já faz em formulários nativos.

Aplicação com NgModule

Na entrada tradicional, importe WebcomponentsAngularModule de @govbr-ds/webcomponents-angular. O método forRoot() registra os Custom Elements e os value accessors do Angular.

import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { WebcomponentsAngularModule } from '@govbr-ds/webcomponents-angular';

@NgModule({
imports: [ReactiveFormsModule, WebcomponentsAngularModule.forRoot()],
})
export class AppModule {}

Aplicação Angular standalone

Em uma aplicação standalone, importe os proxies e os validadores pela entrada @govbr-ds/webcomponents-angular/standalone:

import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import {
BrButton,
BrInput,
FormValidityValidator,
} from '@govbr-ds/webcomponents-angular/standalone';

@Component({
selector: 'app-user-form',
standalone: true,
imports: [ReactiveFormsModule, BrInput, BrButton, FormValidityValidator],
template: `
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<br-input label="E-mail" formControlName="email" type="email" required></br-input>
<br-button type="submit">Salvar</br-button>
</form>
`,
})
export class UserFormComponent {
readonly userForm = new FormGroup({
email: new FormControl('', { nonNullable: true }),
});

onSubmit() {
if (this.userForm.valid) console.log(this.userForm.getRawValue());
}
}

Não misture WebcomponentsAngularModule com os proxies standalone no mesmo componente.

valueChanges, eventos customizados e zonas

O value accessor não chama onChange quando a alteração vem do código. Para observar alterações aceitas pelo formulário use valueChanges; para observar a interação nativa use input/change no host. Callbacks fora da zona devem voltar para o Angular com NgZone.run():

import { ChangeDetectorRef, ElementRef, NgZone, ViewChild, inject } from '@angular/core';

// No componente que possui o formulário:
@ViewChild('select', { read: ElementRef })
private readonly select!: ElementRef<HTMLBrSelectElement>;
private readonly zone = inject(NgZone);
private readonly cdr = inject(ChangeDetectorRef);
private validationMessage = '';

this.userForm.controls.email.valueChanges.subscribe((value) => {
console.log('valor aceito pelo formulário:', value);
});

this.select.nativeElement.addEventListener('brSelectValidationChange', (event: CustomEvent<{ message: string | null }>) => {
this.zone.run(() => {
this.validationMessage = event.detail.message ?? '';
this.cdr.markForCheck();
});
});

No template, associe a referência ao host: <br-select #select ...></br-select>. Em aplicações sem zone, atualize o estado explicitamente e chame o ChangeDetectorRef após o evento.


🟢 Integração com Vue (v-model)

No ecossistema Vue (via pacote @govbr-ds/webcomponents-vue), nossos componentes são exportados para entender a diretiva v-model.

Exemplo usando Composition API

<script setup lang="ts">
import { ref } from 'vue';
import { br-input, br-button } from '@govbr-ds/webcomponents-vue';

const userEmail = ref('');
const errorMsg = ref('');

const submitData = () => {
if (!userEmail.value.includes('@')) {
errorMsg.value = 'E-mail inválido';
} else {
errorMsg.value = '';
console.log('Enviado:', userEmail.value);
}
};
</script>

<template>
<form @submit.prevent="submitData">
<!-- v-model funcionando nativamente com o wrapper -->
<br-input
label="Seu Email"
v-model="userEmail"
type="email"
required
:state="errorMsg ? 'danger' : 'info'"
></br-input>

<br-button type="submit">Processar</br-button>
</form>
</template>

Sincronização externa e eventos customizados

No Vue, v-model é a fonte reativa do valor. Eventos DOM devem ser tratados com @input/@change; eventos customizados usam $event.detail:

<script setup lang="ts">
import { ref } from 'vue';

const city = ref('');
const validationMessage = ref('');

function onValidation(event: CustomEvent<{ message: string | null }>) {
validationMessage.value = event.detail.message ?? '';
}
</script>

<template>
<br-select
v-model="city"
:options="[{ label: 'Brasília', value: 'df' }]"
@brSelectValidationChange="onValidation"
/>
<br-message v-if="validationMessage" state="danger">{{ validationMessage }}</br-message>
</template>

Contrato nativo de validação

Os controles associados a formulário participam de FormData, respeitam required, limites, padrões e comprimentos e expõem a mesma informação de validade por métodos assíncronos:

type FormValidationState = ValidityState & {
valid: boolean;
validationMessage: string;
willValidate: boolean;
};

const field = document.querySelector('br-input');
await field.setCustomValidity(cpfValido(field.value) ? '' : 'Informe um CPF válido.');
const state = await field.getValidationState();
if (!state.valid) await field.reportValidity();

checkValidity() apenas consulta e pode emitir invalid quando inválido. reportValidity() consulta e apresenta a mensagem. setCustomValidity('mensagem') torna o controle inválido; passe '' para limpar. O evento invalid é cancelável e não borbulha, como no HTML nativo.

Constraints por componente

ComponenteConstraintsEstado consultável
br-inputtype, required, pattern, min, max, step, minlength, maxlengthvalue
br-textarearequired, readonly, minlength, maxlengthvalue
br-selectrequired, min-selections, max-selectionsvalue
br-radio-grouprequiredvalue
br-checkbox, br-radio, br-switchrequiredchecked, value
br-slidermin, max, step; intervalo ordenadovalue, rangeValue
br-datetime-pickerrequired, min, max; intervalo completo e ordenadovalue, rangeValue
br-uploadrequired, min-files, max-files, accept, max-file-sizefiles
br-tag com interaction-selectrequired, min-selections, max-selectionsselected, value

Constraints HTML ficam no componente. CPF, confirmação de senha, regras de negócio e dependências entre campos ficam na aplicação e devem ser sincronizadas com setCustomValidity.

Todos os campos de formulário possuem o slot feedback para a mensagem visual. Quando o slot não é preenchido e existe uma mensagem customizada, o componente renderiza um br-message padrão. A referência acessível usa aria-describedby e aria-errormessage; consulte a seção Validação de cada componente para os detalhes do seu valor e das suas constraints.

Validadores síncronos e assíncronos

br-input, br-textarea, br-select e br-radio-group também aceitam a propriedade validator para regras de domínio que não cabem nas constraints HTML. O contrato é executado no change ou quando validate() é chamado, e retorna null em caso de sucesso ou uma mensagem de erro:

const validator = async (value: string) => {
const disponivel = await verificarDisponibilidade(value);
return disponivel ? null : 'Esse valor já está em uso.';
};

const field = document.querySelector('br-input');
field.validator = validator;
if (!(await field.validate())) return;

No modo múltiplo, br-select recebe string[] em vez de string. Em React, Angular e Vue, passe a função como propriedade com validator={validator}, [validator]="validator" ou :validator="validator"; atributos HTML não transportam funções.

Durante a execução assíncrona, esses campos expõem aria-busy="true", o slot validation-loading e um evento específico de mudança de validação. Consulte a seção Validação do componente para detalhes, estados de concorrência, mensagens e exemplos.

Mensagem, acessibilidade e eventos

Mostre o erro em texto e associe-o ao campo com aria-describedby ou aria-errormessage. Ao apresentar o erro, mantenha aria-invalid="true"; não dependa somente de state="danger", cor ou ícone. O label visível deve fazer parte do nome acessível.

<br-input id="cpf" label="CPF" required aria-errormessage="cpf-erro"></br-input>
<br-message id="cpf-erro" state="danger" role="alert">
Informe um CPF válido.
</br-message>

input acompanha edição e change representa seleção ou commit. Leia o estado em event.target.value, checked, files, selected ou rangeValue; não espere payload em detail. Alterações externas, form.reset() e restauração de estado não emitem eventos de usuário.

form.addEventListener('submit', async (event) => {
event.preventDefault();
const cpf = form.elements.cpf;
await cpf.setCustomValidity(isCpfValid(cpf.value) ? '' : 'Informe um CPF válido.');
if (!(await form.reportValidity())) return;
salvar(new FormData(form));
});

No Angular, FormValidityValidator é declarado pelo módulo e também exportado pela entrada standalone; enquanto a Promise de validação está em andamento o controle fica PENDING. React usa eventos nativos e refs tipadas. Vue usa v-model, incluindo argumentos para checked, files, selected e rangeValue quando aplicável.

Contrato canônico dos quickstarts

As abordagens JavaScript, Angular, React e Vue usam este mesmo contrato. A mensagem indicada é sincronizada com setCustomValidity quando a regra não pertence ao HTML.

CampoRegraMensagem
nomeobrigatório, 5–100 caracteresNome deve ter no mínimo 5 caracteres.
e-mailobrigatório, type=email, até 120 caracteresInforme um e-mail válido.
idadeobrigatório, inteiro entre 18 e 120A idade mínima é 18 anos.
CPFobrigatório e dígitos verificadores válidosInforme um CPF válido.
celularobrigatório, 10 ou 11 dígitosInforme um celular válido.
CEPobrigatório, 8 dígitosInforme um CEP válido.
cidadeseleção obrigatóriaSelecione sua cidade.
descriçãoobrigatória, 10–200 caracteresO resumo deve ter entre 10 e 200 caracteres.
contatouma opção obrigatória no grupoSelecione como conheceu o projeto.
uploadao menos um arquivoEnvie um documento probatório.
senhaobrigatória, 8–128 caracteresA senha deve ter no mínimo 8 caracteres.
confirmaçãoigual à senhaAs senhas devem ser iguais.
termoscheckbox obrigatórioVocê deve aceitar os termos.
switch, slider, datetime e tagsopcionais; slider entre 0 e 10

Criação, edição, reset, listagem e persistência devem preservar esse contrato. Os testes Playwright de cada quickstart exercitam as rotas de todas as abordagens e a publicação em base path.