> ## Documentation Index
> Fetch the complete documentation index at: https://docs.serval.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Fields and layout

> Every field type a form can ask for, the validation each supports, and how to arrange them into sections.

Describe the fields you want to Catalyst. This page is the reference for what's available.

## Field types

| Field         | Asks for                                                                       | Submitted value      |
| :------------ | :----------------------------------------------------------------------------- | :------------------- |
| `text`        | Free text, single- or multi-line                                               | The string           |
| `number`      | A numeric value                                                                | The number           |
| `boolean`     | A yes/no toggle                                                                | `true` or `false`    |
| `select`      | One option from a list                                                         | The option's value   |
| `multiSelect` | Several options from a list                                                    | A list of values     |
| `cards`       | One option from a visual grid                                                  | The option's value   |
| `date`        | A calendar date                                                                | `"YYYY-MM-DD"`       |
| `time`        | A time of day                                                                  | 24-hour `"HH:MM"`    |
| `datetime`    | A specific instant                                                             | RFC 3339 in UTC      |
| `duration`    | A length of time                                                               | Whole seconds        |
| `attachment`  | A file upload                                                                  | The attachment ID    |
| `entity`      | One record from a [database](/sections/documentation/databases/overview) table | The record ID        |
| `entityList`  | Several database records                                                       | A list of record IDs |
| `user`        | One person in your organization                                                | The user ID          |
| `userList`    | Several people                                                                 | A list of user IDs   |

Every field takes a **label**, optional **help** text under the label, and a **message** that replaces the default error copy.

## Validation

Serval enforces constraints, not the browser. A violation shows an error on the field and blocks submit on every surface.

| Field type                              | Constraints                                                                        |
| :-------------------------------------- | :--------------------------------------------------------------------------------- |
| `text`                                  | `format: "email" \| "url"`, `pattern`, `minLength`, `maxLength`                    |
| `number`                                | `min`, `max` (inclusive)                                                           |
| `date`                                  | `min`, `max` as absolute dates: `"2026-03-01"`                                     |
| `time`                                  | `min`, `max` as 24-hour times: `"17:30"`                                           |
| `datetime`                              | `min`, `max` as RFC 3339. A bound with no time zone reads as UTC                   |
| `duration`                              | `min`, `max` in seconds. Write them as arithmetic: `30 * 86400`                    |
| `multiSelect`, `entityList`, `userList` | `minItems`, `maxItems`                                                             |
| `attachment`                            | `acceptedFileTypes`: MIME types, wildcards (`"image/*"`), or extensions (`".csv"`) |

<Note>
  **Constraints never fire on an empty answer.** `required` owns emptiness. An optional field with a `maxLength` is checked only once somebody types in it.
</Note>

<Warning>
  Date bounds are absolute. There's no "no earlier than today," because the form renders without a trustworthy clock. A rule that moves with the calendar belongs in the submit workflow.
</Warning>

## Choice fields

`select` and `multiSelect` options can carry a `description` shown as secondary text. Slack truncates descriptions after 75 characters. The submitted value is unchanged.

`cards` renders options as a grid of tiles with optional images: the right choice when somebody is picking a *thing*, like a laptop model or an office location.

```tsx theme={null}
const [model, Model] = field.cards("model", [
  {
    value: "mba",
    label: "MacBook Air (M-series)",
    description: "Lightweight macOS laptop for everyday work and travel.",
    imageUrl: "https://example.com/images/macbook-air.png",
  },
], { label: "Choose your laptop", columns: 2 });
```

<Note>
  Card images must be `https` URLs and load directly in the filler's browser. Serval doesn't host or proxy them, so use an image that's publicly reachable and expected to stay that way. `columns` accepts 2, 3, or 4 and defaults to 3.
</Note>

## Database and user fields

`entity` and `entityList` turn a [database](/sections/documentation/databases/overview) table into a searchable picker, so requesters choose real records instead of typing names that match nothing. You pick which field shows as the label, which is submitted as the value, and which appears as secondary text. Filters can key off *another field's current answer*, so choosing an application narrows the entitlements below it.

`user` and `userList` pick from your organization:

| Filter               | Effect                                     |
| :------------------- | :----------------------------------------- |
| `teamId`             | Only members of that team                  |
| `groupId`            | Only members of that group                 |
| `roles`              | Only users holding one of the listed roles |
| `includeDeactivated` | Include deactivated users. Off by default  |

`teamId` and `groupId` are mutually exclusive. Setting both fails at render time rather than dropping a filter.

## Layout

| Element   | Purpose                                                                                          |
| :-------- | :----------------------------------------------------------------------------------------------- |
| `Section` | Groups questions under a heading with an optional icon. `columns={2}` lays them out side by side |
| `Notice`  | A call-out. `tone` is `info`, `success`, `warning`, or `danger`                                  |
| `Divider` | A rule between groups                                                                            |

The `<Form>` root carries identity and look: `header` (title, subtitle, icon), `accent` (`brand`, `blue`, `green`, `yellow`, `orange`, `red`), `density` (`comfortable`, `compact`), and `surface` (`plain`, `tinted`, `gradient`).

```tsx theme={null}
<Form header={{ title: "Laptop Request", icon: "laptop" }} accent="brand">
  <Section title="Device" icon="laptop">
    <Model required />
  </Section>
  <Divider />
  <Section title="Details" icon="clipboard" columns={2}>
    <ForWhom />
    <NeededBy />
  </Section>
</Form>
```

<Note>
  Colors and icons come from fixed lists, so forms match the rest of the product and render correctly everywhere, including Slack. An unrecognized name fails at publish instead of rendering nothing.
</Note>

***

<CardGroup cols={2}>
  <Card title="Logic and workflows" icon="code-branch" href="/sections/documentation/forms/logic-and-workflows">
    Conditional fields and live data from your systems.
  </Card>

  <Card title="Share and fill" icon="share-nodes" href="/sections/documentation/forms/share-and-fill">
    Where forms appear and how each surface renders them.
  </Card>
</CardGroup>
