Fieldsets own structure. Each field is label → small → control. The
small holds state, not a caption. Everything fills the width. Datastar
submits. The handler answers 204 and the stream re-lands the page.
html
<form data-on:submit__prevent="@post('/o/1/settings/warehouses', {contentType: 'form'})">
<fieldset>
<legend>Warehouse <small>where the trucks load</small></legend>
<label>
<span>Name</span>
<small></small>
<input name="name" required autocomplete="off"/>
</label>
<label style="--row-width: 2;">
<span>Address</span>
<small></small>
<input name="address"/>
</label>
<label>
<span>Kind</span>
<small></small>
<select name="kind">
<option value="warehouse">Warehouse</option>
<option value="office">Office</option>
</select>
</label>
</fieldset>
<div class="spread">
<button type="reset" class="sec">Cancel</button>
<button type="submit" class="pri">Save</button>
</div>
</form>
Rules
- The field's
smallis empty at rest. It carries the validation message or a "saved". Captions go in the legend. --row-widthon a label spans it across the fieldset's columns.- The server judges a stored value:
aria-invalid="true"on the control, the message in itssmall. - A record's form is the edit card: a pencil unlocks, Cancel is a native
type="reset", Save posts. @post(url, {contentType: 'form'})sends the form's fields. No signal mirrors an input.- A wizard lights the next control with
data-ui-state="guide".