Skip to content
v0.2.10

Grouped form

Shared form tracks, semantic groups and optional fields composed from Flood primitives.

Use a shared outer grid for peer groups, with the same surface padding and field spacing. Each group owns its title and reading order. Two short fields stack inside each group; the next row reuses the same tracks instead of inventing a new four-column or uneven grid. This recipe illustrates workspace preferences, not an invoice-specific screen or a universal two-column requirement.

Choose the number of tracks from the task and declared viewport. A long-address group may span the row; a short field does not need its own column. Keep related fields together in DOM order, including when an optional field disappears. Use Grid, Fieldset, Surface and Field rather than recreating their recipes.

Import the shared stylesheet once and render inside FloodRoot. onApply receives native FormData in the host; this recipe implements no persistence or permissions. Those limits belong here in documentation, not as repeated captions in the form.

Source

TSX
"use client";
import type {FormEvent} from 'react';
import {Button,Field,Fieldset,Grid,Input,Select,Stack,Surface} from '@flood-md/react';

/** Shared outer tracks; groups retain their own reading order and field ownership. */
export function GroupedForm({showBillingEmail=true,onApply}:{showBillingEmail?:boolean;onApply?:(values:FormData)=>void}) {
  function submit(event:FormEvent<HTMLFormElement>){event.preventDefault();onApply?.(new FormData(event.currentTarget));}
  return <form aria-label="Workspace settings" onSubmit={submit}>
    <Stack gap="8">
      <Grid columns={2} gap="6">
        <Surface padding="6"><Fieldset legendSize="section" legend="Identity"><Stack gap="6">
          <Field label="Workspace name" required>{props=><Input {...props} name="workspace" required />}</Field>
          <Field label="Reference">{props=><Input {...props} name="reference" />}</Field>
        </Stack></Fieldset></Surface>
        <Surface padding="6"><Fieldset legendSize="section" legend="Locale"><Stack gap="6">
          <Field label="Language">{props=><Select {...props} name="language" defaultValue="en"><option value="en">English</option><option value="ru">Русский</option></Select>}</Field>
          <Field label="Time zone">{props=><Select {...props} name="timezone" defaultValue="UTC"><option value="UTC">UTC</option><option value="Asia/Almaty">Almaty</option></Select>}</Field>
        </Stack></Fieldset></Surface>
        <Surface padding="6"><Fieldset legendSize="section" legend="Primary contact"><Stack gap="6">
          <Field label="Contact name">{props=><Input {...props} name="contact" />}</Field>
          <Field label="Contact email">{props=><Input {...props} name="contactEmail" type="email" />}</Field>
        </Stack></Fieldset></Surface>
        <Surface padding="6"><Fieldset legendSize="section" legend="Billing contact"><Stack gap="6">
          <Field label="Billing name">{props=><Input {...props} name="billing" />}</Field>
          {showBillingEmail&&<Field label="Billing email">{props=><Input {...props} name="billingEmail" type="email" />}</Field>}
        </Stack></Fieldset></Surface>
      </Grid>
      <Button type="submit">Apply settings</Button>
    </Stack>
  </form>;
}

Review

Compare left/right edges and gutters in both rows, label/control associations, native required submission and omitted optional content. Inspect long labels and enlarged text at the host's declared widths. Do not judge a form by zero page overflow alone. Read the forms workflow and content workflow before adapting it. A useful form does not need a helper paragraph under every title.

Independent sections use Fieldset legendSize="section" and keep one shared outer Grid across peer groups. Field labels retain the compact inner gap; Stack owns row separation. The hierarchy workflow explains why section titles and choice captions are different roles.