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.
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>
);
}
🔴 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.
Módulo ou Standalone Component
Você precisa se certificar que o GovbrDsWebcomponentsModule foi importado na aplicação, pois é ele que injeta as definições corretas no módulo de forms do Angular.
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-user-form',
standalone: true,
imports: [ReactiveFormsModule, GovbrDsWebcomponentsModule],
template: `
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<!-- O CVA atualiza value e a bridge assíncrona importa ValidityState. -->
<br-input
label="Email"
formControlName="email"
type="email"
required
[state]="emailControl.invalid && emailControl.touched ? 'danger' : 'info'"
></br-input>
<br-button type="submit">Salvar</br-button>
</form>
`
})
export class UserFormComponent {
userForm = new FormGroup({
email: new FormControl('', { nonNullable: true }),
});
get emailControl() {
return this.userForm.get('email')!;
}
onSubmit() {
console.log(this.userForm.value);
}
}
🟢 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>
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
| Componente | Constraints | Estado consultável |
|---|---|---|
br-input | type, required, pattern, min, max, step, minlength, maxlength | value |
br-textarea | required, readonly, minlength, maxlength | value |
br-select | required, min-selections, max-selections | value |
br-checkbox, br-radio, br-switch | required | checked, value |
br-slider | min, max, step; intervalo ordenado | value, rangeValue |
| datetime | required, min, max; intervalo completo e ordenado | value, rangeValue |
br-upload | required, min-files, max-files, accept, max-file-size | files |
br-tag[interaction-select] | required, min-selections, max-selections | selected, 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.
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.
| Campo | Regra | Mensagem |
|---|---|---|
| nome | obrigatório, 5–100 caracteres | Nome deve ter no mínimo 5 caracteres. |
obrigatório, type=email, até 120 caracteres | Informe um e-mail válido. | |
| idade | obrigatório, inteiro entre 18 e 120 | A idade mínima é 18 anos. |
| CPF | obrigatório e dígitos verificadores válidos | Informe um CPF válido. |
| celular | obrigatório, 10 ou 11 dígitos | Informe um celular válido. |
| CEP | obrigatório, 8 dígitos | Informe um CEP válido. |
| cidade | seleção obrigatória | Selecione sua cidade. |
| descrição | obrigatória, 10–200 caracteres | O resumo deve ter entre 10 e 200 caracteres. |
| contato | uma opção obrigatória no grupo | Selecione como conheceu o projeto. |
| upload | ao menos um arquivo | Envie um documento probatório. |
| senha | obrigatória, 8–128 caracteres | A senha deve ter no mínimo 8 caracteres. |
| confirmação | igual à senha | As senhas devem ser iguais. |
| termos | checkbox obrigatório | Você deve aceitar os termos. |
| switch, slider, datetime e tags | opcionais; 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.