Skip to content

Formulários

O Slash fornece helpers tipados para trabalhar com formulários de forma simples e eficiente. Não há “magia” - você usa HTML nativo e adiciona reatividade com createState() e os helpers de form.

  1. Manual: Controle total usando createState() e event handlers
  2. Two-way binding: Helpers para sincronização automática (ex: textFieldControl)
  3. Form data: Extrair dados do form com formToObject() e onSubmit()

A forma mais simples é usar createState() e event handlers:

import { html, createState } from '@ezbug/slash'
function LoginForm() {
const form = createState({
email: '',
password: ''
})
const handleSubmit = (e: Event) => {
e.preventDefault()
const data = form.get()
console.log('Login:', data)
}
return html`
<form onSubmit=${handleSubmit}>
<div>
<label>Email:</label>
<input
type="email"
value=${form.get().email}
onInput=${(e: Event) => {
const current = form.get()
form.set({
...current,
email: (e.target as HTMLInputElement).value
})
}}
/>
</div>
<div>
<label>Senha:</label>
<input
type="password"
value=${form.get().password}
onInput=${(e: Event) => {
const current = form.get()
form.set({
...current,
password: (e.target as HTMLInputElement).value
})
}}
/>
</div>
<button type="submit">Entrar</button>
</form>
`
}

O Slash fornece helpers para binding automático:

textFieldControl - Text inputs e textareas

Section titled “textFieldControl - Text inputs e textareas”
import { html, createState } from '@ezbug/slash'
import { textFieldControl } from '@ezbug/slash'
function TextForm() {
const nameState = createState({ value: '' })
const bioState = createState({ value: '' })
// Control para <input>
const nameControl = textFieldControl(nameState, 'input')
// Control para <textarea>
const bioControl = textFieldControl(bioState, 'input')
return html`
<form>
<div>
<label>Nome:</label>
<input type="text" ...${nameControl} />
</div>
<div>
<label>Bio:</label>
<textarea ...${bioControl}></textarea>
</div>
<p>Nome: ${nameState.get().value}</p>
<p>Bio: ${bioState.get().value}</p>
</form>
`
}

textFieldControl aceita três modos:

ModoQuando atualizaUse quando
"input"A cada tecla digitadaFeedback instantâneo (default)
"change"Ao sair do campo (blur)Validação menos frequente
"both"Nos dois eventosMáxima compatibilidade
// Input mode (default)
const ctrl1 = textFieldControl(state, 'input')
// Change mode
const ctrl2 = textFieldControl(state, 'change')
// Ambos
const ctrl3 = textFieldControl(state, 'both')
import { html, createState } from '@ezbug/slash'
import { checkboxControl } from '@ezbug/slash'
function TermsForm() {
const termsState = createState({ value: false })
const newsState = createState({ value: true })
const termsCtrl = checkboxControl(termsState)
const newsCtrl = checkboxControl(newsState)
return html`
<form>
<label>
<input type="checkbox" ...${termsCtrl} />
Aceito os termos
</label>
<label>
<input type="checkbox" ...${newsCtrl} />
Receber newsletter
</label>
<p>Termos aceitos: ${termsState.get().value ? 'Sim' : 'Não'}</p>
<p>Newsletter: ${newsState.get().value ? 'Sim' : 'Não'}</p>
</form>
`
}
import { html, createState } from '@ezbug/slash'
import { radioControl } from '@ezbug/slash'
function PreferenceForm() {
const themeState = createState({ value: 'light' })
const lightCtrl = radioControl(themeState, 'light')
const darkCtrl = radioControl(themeState, 'dark')
const autoCtrl = radioControl(themeState, 'auto')
return html`
<form>
<p>Escolha o tema:</p>
<label>
<input type="radio" name="theme" ...${lightCtrl} />
Claro
</label>
<label>
<input type="radio" name="theme" ...${darkCtrl} />
Escuro
</label>
<label>
<input type="radio" name="theme" ...${autoCtrl} />
Automático
</label>
<p>Tema selecionado: ${themeState.get().value}</p>
</form>
`
}
import { html, createState } from '@ezbug/slash'
import { SelectControl } from '@ezbug/slash'
function CountryForm() {
const countryState = createState({ value: 'br' })
const countryCtrl = SelectControl(countryState)
return html`
<form>
<label>
País:
<select ...${countryCtrl}>
<option value="br">Brasil</option>
<option value="us">Estados Unidos</option>
<option value="pt">Portugal</option>
<option value="es">Espanha</option>
</select>
</label>
<p>País selecionado: ${countryState.get().value}</p>
</form>
`
}

Converte um <form> em um objeto JavaScript:

import { formToObject } from '@ezbug/slash'
const form = document.querySelector('form')!
const data = formToObject(form)
console.log(data)
// { email: "user@example.com", password: "secret", terms: "on" }

Previne o default e extrai dados automaticamente:

import { html } from '@ezbug/slash'
import { onSubmit } from '@ezbug/slash'
function ContactForm() {
const handleSubmit = onSubmit((data, e) => {
console.log('Dados:', data)
// data = { name: "...", email: "...", message: "..." }
// Fazer POST para API
fetch('/api/contact', {
method: 'POST',
body: JSON.stringify(data)
})
})
return html`
<form onSubmit=${handleSubmit}>
<input type="text" name="name" placeholder="Nome" />
<input type="email" name="email" placeholder="Email" />
<textarea name="message" placeholder="Mensagem"></textarea>
<button type="submit">Enviar</button>
</form>
`
}

O Slash não possui um sistema de validação built-in, mas você pode criar facilmente com createState():

import { html, createState } from '@ezbug/slash'
function SignupForm() {
const form = createState({
email: '',
password: '',
errors: {
email: '',
password: ''
}
})
const validate = () => {
const state = form.get()
const errors = { email: '', password: '' }
// Validar email
if (!state.email) {
errors.email = 'Email é obrigatório'
} else if (!state.email.includes('@')) {
errors.email = 'Email inválido'
}
// Validar senha
if (!state.password) {
errors.password = 'Senha é obrigatória'
} else if (state.password.length < 6) {
errors.password = 'Senha deve ter no mínimo 6 caracteres'
}
form.set({ ...state, errors })
return !errors.email && !errors.password
}
const handleSubmit = (e: Event) => {
e.preventDefault()
if (validate()) {
console.log('Formulário válido!', form.get())
}
}
return html`
<form onSubmit=${handleSubmit}>
<div>
<input
type="email"
placeholder="Email"
value=${form.get().email}
onInput=${(e: Event) => {
const state = form.get()
form.set({
...state,
email: (e.target as HTMLInputElement).value,
errors: { ...state.errors, email: '' }
})
}}
/>
${form.get().errors.email &&
html`<span class="error">${form.get().errors.email}</span>`}
</div>
<div>
<input
type="password"
placeholder="Senha"
value=${form.get().password}
onInput=${(e: Event) => {
const state = form.get()
form.set({
...state,
password: (e.target as HTMLInputElement).value,
errors: { ...state.errors, password: '' }
})
}}
/>
${form.get().errors.password &&
html`<span class="error">${form.get().errors.password}</span>`}
</div>
<button type="submit">Cadastrar</button>
</form>
`
}
import { html, createState } from '@ezbug/slash'
function UsernameForm() {
const state = createState({
username: '',
checking: false,
error: ''
})
const checkUsername = async (username: string) => {
state.set({ ...state.get(), checking: true, error: '' })
try {
const res = await fetch(`/api/check-username?name=${username}`)
const { available } = await res.json()
state.set({
...state.get(),
checking: false,
error: available ? '' : 'Username já existe'
})
} catch (err) {
state.set({
...state.get(),
checking: false,
error: 'Erro ao verificar'
})
}
}
return html`
<form>
<input
type="text"
placeholder="Username"
value=${state.get().username}
onInput=${(e: Event) => {
const username = (e.target as HTMLInputElement).value
state.set({ ...state.get(), username })
}}
onBlur=${() => {
const username = state.get().username
if (username) checkUsername(username)
}}
/>
${state.get().checking && html`<span>Verificando...</span>`}
${state.get().error && html`<span class="error">${state.get().error}</span>`}
</form>
`
}

O Slash exporta tipos completos para todos os eventos de formulário:

import type {
TextFieldEvent,
CheckboxEvent,
RadioEvent,
SelectEvent,
FormSubmitEvent,
FormResetEvent,
ButtonEvent
} from '@ezbug/slash'
// Text field (input/textarea)
function handleInput(e: TextFieldEvent) {
console.log(e.target.value)
}
// Checkbox
function handleCheck(e: CheckboxEvent) {
console.log(e.target.checked)
}
// Radio
function handleRadio(e: RadioEvent) {
console.log(e.target.value, e.target.checked)
}
// Select
function handleSelect(e: SelectEvent) {
console.log(e.target.value)
}
// Form submit
function handleSubmit(e: FormSubmitEvent) {
e.preventDefault()
console.log(e.currentTarget) // HTMLFormElement
}

Funções utilitárias para extrair valores de eventos:

import { getText, getChecked, getSelectValue } from '@ezbug/slash'
// Text field
const handleInput = (e: TextFieldEvent) => {
const value = getText(e) // string
console.log(value)
}
// Checkbox/Radio
const handleCheck = (e: CheckboxEvent) => {
const checked = getChecked(e) // boolean
console.log(checked)
}
// Select
const handleSelect = (e: SelectEvent) => {
const value = getSelectValue(e) // string
console.log(value)
}