Native forms
Every element that holds a value is a form control, like <input> or <select>. Give it a name
inside a <form> and it submits, validates and resets with the form, with no JavaScript of your
own.
import { useState, type SubmitEvent } from 'react';import { GuiButton, GuiTextinput } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function TextInputNativeForm() { const [result, setResult] = useState('');
const onSubmit = (event: SubmitEvent<HTMLFormElement>) => { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget)); setResult(`Submitted ${JSON.stringify(data)}`); };
return ( <form id="newsletter" onSubmit={onSubmit}> <div className="gui-grid gui-grid--row"> <div className="gui-grid__cell gui-grid__cell--span-3"> <GuiTextinput label="Email" name="email" required /> </div> <div className="gui-grid__cell"> <GuiButton type="submit" label="Subscribe" /> </div> </div> <p id="newsletter-result" role="status"> {result} </p> </form> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import '@golemui/gui-components/button';import '@golemui/gui-components/textinput';
@Component({ selector: 'app-text-input-native-form', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <form id="newsletter" #form (submit)="onSubmit($event, form)"> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-3"> <gui-textinput label="Email" name="email" required></gui-textinput> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Subscribe"></gui-button> </div> </div> <p id="newsletter-result" role="status">{{ result() }}</p> </form> `,})export class TextInputNativeFormComponent { result = signal('');
onSubmit(event: SubmitEvent, form: HTMLFormElement) { event.preventDefault(); const data = Object.fromEntries(new FormData(form)); this.result.set(`Submitted ${JSON.stringify(data)}`); }}<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/button';import '@golemui/gui-components/textinput';import '@golemui/gui-components/index.css';
const result = ref('');
function onSubmit(event: SubmitEvent) { if (!(event.currentTarget instanceof HTMLFormElement)) return; const data = Object.fromEntries(new FormData(event.currentTarget)); result.value = `Submitted ${JSON.stringify(data)}`;}</script>
<template> <form id="newsletter" @submit.prevent="onSubmit"> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-3"> <gui-textinput label="Email" name="email" :required="true" ></gui-textinput> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Subscribe"></gui-button> </div> </div> <p id="newsletter-result" role="status">{{ result }}</p> </form></template>import { LitElement, html } from 'lit';import { customElement, query, state } from 'lit/decorators.js';import '@golemui/gui-components/button';import '@golemui/gui-components/textinput';import '@golemui/gui-components/index.css';
@customElement('text-input-native-form')export class TextInputNativeForm extends LitElement { @query('#newsletter') private form!: HTMLFormElement; @state() private result = '';
protected override createRenderRoot() { return this; }
private onSubmit(event: SubmitEvent) { event.preventDefault(); const data = Object.fromEntries(new FormData(this.form)); this.result = `Submitted ${JSON.stringify(data)}`; }
override render() { return html` <form id="newsletter" @submit=${this.onSubmit}> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-3"> <gui-textinput label="Email" name="email" required></gui-textinput> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Subscribe"></gui-button> </div> </div> <p id="newsletter-result" role="status">${this.result}</p> </form> `; }}<form id="newsletter"> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-3"> <gui-textinput label="Email" name="email" required></gui-textinput> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Subscribe"></gui-button> </div> </div> <p id="newsletter-result" role="status"></p></form>
<script type="module"> const form = document.querySelector('#newsletter');
form.addEventListener('submit', (event) => { event.preventDefault(); const data = Object.fromEntries(new FormData(form)); document.querySelector('#newsletter-result').textContent = `Submitted ${JSON.stringify(data)}`; });</script>Taking part
Section titled “Taking part”An element takes part in its form only when it has a name. Without one it stays out: it
submits nothing and never blocks a submit. That keeps an element that only displays a value, or
one inside a composite element, out of the form.
The element belongs to the <form> around it. Read it back from the element’s form property.
What an element submits
Section titled “What an element submits”The form submits the value under the element’s name, and FormData reads it like any other
field:
| Value | Submitted as |
|---|---|
| Text or a number | The text, such as ada@example.com or 3. |
true, such as a checked checkbox | on, like a native checkbox. |
| A list, such as the values of a tags field | One entry per item, all under the same name. |
| An object, such as a date range | JSON. |
Empty: no value, an empty string or list, false | Nothing. |
Each component page says what that element submits, in its “In a form” section.
Validation
Section titled “Validation”An element with a name validates like a native control:
requiredblocks a submit while the element is empty;- bounds, such as
minimumandmaximumon a number ormin-dateandmax-dateon a date or a range of dates, block a value outside them; - a disabled date or time range blocks a value inside it.
They check the value however it got there, typed by the user or set from your code.
The browser stops the submit, focuses the control and shows its message, pointing at the part of
the element that holds the value. The messages are in English by default; translate them like the
other built-in strings, under keys such as valueMissing, rangeUnderflow and minDate. See
i18n.
The element has the validation API of a native control:
| Member | What it does |
|---|---|
checkValidity() | Returns whether the value is valid, without showing anything. |
reportValidity() | The same, and shows the browser’s message when it isn’t. |
validity | The ValidityState, such as validity.valueMissing. |
validationMessage | The message the browser would show. |
form | The <form> the element belongs to. |
Your own validation
Section titled “Your own validation”The errors attribute shows messages from your own validation, under the field, and marks it as
invalid for assistive technology. It doesn’t block the submit: check the values in your submit
handler, or set the errors from a gui-change listener as the user works.
touched holds the errors back: while it is false, the field doesn’t show them. Leave it unset,
unless you validate before the user has touched the field and set it to true once they have.
Reset, disabled and read-only
Section titled “Reset, disabled and read-only”form.reset()restores the value each element started with.- A disabled element, by its
disabledattribute or property or a disabled<fieldset>around it, can’t be changed, isn’t validated, and the form doesn’t submit it. Setting the property sets the attribute, as on a native control. - A read-only element can be focused and copied but not changed. It is submitted and isn’t validated, like a native read-only input.
When the browser restores a page, for example on the back button, the elements get their values back like native controls.