[Installation](/getting-started/installation) ends with a provider and one `Button` on the
page. The next thing most people write is a `div` with a class name, and this page exists to
break that habit: a screen is composed from components, and every one of them states
something the markup would otherwise have to remember, a landmark, an accessible name, an
error announcement, a submit control that cannot be pressed twice.

The screen below invites a teammate by email. Three steps, about forty lines, one file.

## 1. Start from the shell

```tsx
import { Page, PageContent, PageHeader, Typography } from "@asheeui/web";

<Page>
  <PageHeader>
    <Typography role="heading-xl">Invite a teammate</Typography>
  </PageHeader>
  <PageContent>{/* the form goes here */}</PageContent>
</Page>;
```

Four components and no class names. `Page` claims the height of the window and paints the
theme background; `PageHeader` is the `header` landmark and holds the bar at the top;
`PageContent` is the `main` landmark and takes the height the other two leave. `Typography`
renders the element its role implies, `heading-xl` is an `h1`, so the one heading on the
screen is a real level-one heading without anyone stating it.

The shell adds no navigation, no data and no breakpoint. Each option belongs to the part that can
act on it, which is why `<PageContent spacing="lg">` moves the content under the bar and
`<Page sticky>` does not compile at all, a shell has no bar to keep in view.
[Page](/components/layouts/page) has that split in full.

## 2. Put one field in it

```tsx
import { Form, Input } from "@asheeui/web";
import { useState } from "react";

const [email, setEmail] = useState("");
const [message, setMessage] = useState<string>();

<Form legend="Who should join?" submitLabel="Send invite" onSubmit={submit}>
  <Input
    label="Email"
    name="email"
    type="email"
    description="We send the invite here."
    value={email}
    onChangeText={setEmail}
    status={message ? "error" : "default"}
    message={message}
    required
  />
</Form>;
```

`Input` is one component wearing the field family's four parts: `label` is the accessible
name, `description` is the hint read with the control, and `message` with `status` is the
outcome. Because they are props rather than sibling elements, the component wires the ids
itself, `htmlFor` for the label, `aria-describedby` for the hint and the message,
`aria-invalid` for the status, so a field cannot ship with a message that nothing announces.
[Field anatomy](/concepts/fields) is that shape in full.

The value stays on the page, and the field reports every change through `onChangeText`. That
is what lets the page validate, reset or submit without reaching into the DOM.

## 3. Give it an action

`Form` draws the action itself: `submitLabel` renders a submit `Button` with `type="submit"`
inside the form's element, so <kbd>Enter</kbd> in the field submits it. The page supplies the
rule and the consequence.

```tsx
import { type FormEvent, useState } from "react";
import {
  Form,
  Input,
  Page,
  PageContent,
  PageHeader,
  Typography,
} from "@asheeui/web";

export function InviteTeammate() {
  const [email, setEmail] = useState("");
  const [message, setMessage] = useState<string>();
  const [invited, setInvited] = useState<string>();

  function submit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();

    if (!email.includes("@")) {
      setMessage("An email address needs an @.");
      return;
    }

    setMessage(undefined);
    setInvited(email);
  }

  return (
    <Page>
      <PageHeader>
        <Typography role="heading-xl">Invite a teammate</Typography>
      </PageHeader>
      <PageContent spacing="lg">
        <Form
          legend="Who should join?"
          submitLabel="Send invite"
          onSubmit={submit}>
          <Input
            label="Email"
            name="email"
            type="email"
            description="We send the invite here."
            value={email}
            onChangeText={setEmail}
            status={message ? "error" : "default"}
            message={message}
            required
          />
        </Form>
        {invited && (
          <Typography role="body-sm" tone="muted">
            {invited} now has an invite.
          </Typography>
        )}
      </PageContent>
    </Page>
  );
}
```

This screen leaves one state out: while the request is in flight, pass `isPending` to `Form`
and the submit control is busy and cannot be pressed twice, with its label still in place so
the row does not move. [Building a form](/guides/building-a-form) covers that, and the focus
rule below.

## The component states the meaning, the page states the content

That sentence is the whole approach, and this screen is one example of each half.

| You wrote | It stated | Instead of |
| --- | --- | --- |
| `Page`, `PageHeader`, `PageContent` | The shell, its height, and the `header` and `main` landmarks | A `div` with `min-h-dvh` and a `main` placed by hand |
| `Typography role="heading-xl"` | A level-one heading, and the treatment that role carries | An `h1` with a size and a weight chosen per screen |
| `Form legend submitLabel` | The `fieldset` and its legend, the submit control, and its pending state when you pass `isPending` | A `form`, a `fieldset`, a `legend` and a button |
| `Input label description message status` | The accessible name, the hint, the message, `aria-invalid`, `aria-describedby` | A `label`, an id on a paragraph and `aria-describedby` typed by hand |
| `PageContent` | A gutter and a maximum width | `mx-auto max-w-5xl px-4 sm:px-6` |

The other half is yours, and it is smaller than the markup it replaces: the values, the words
on the label and the description, the sentence that explains the failure, the rule that
decides it, and what happens when it passes. None of that is styling, so none of it is a
class name.

## What it reads like to a screen reader

The reader arrives on this screen and hears, in this order:

- A `banner` landmark, the `PageHeader`, and inside it "Invite a teammate, heading level 1".
- A `main` landmark, `PageContent`, which is also what a skip link points at.
- "Who should join?, group", because the form's fields are grouped by the `fieldset` its
  legend names.
- "Email, edit, required". The label is the name; the requirement is on the control, and the
  asterisk next to the label is only drawn.
- "We send the invite here.", read with the field through `aria-describedby` rather than
  drifting after it as a stray paragraph.
- On a failed submit, the message, announced immediately, without waiting for a pause,
  because the field's message element carries `role="alert"`, and the field reporting itself
  invalid, so it is invalid to assistive technology and not merely red.
- A submit button named "Send invite", which <kbd>Enter</kbd> in the field presses.

That is the framework's half. The one thing it cannot do for you: after a failed submit it
does not know which field to fix first, so the page moves focus to the first invalid control.
[Building a form](/guides/building-a-form) has the effect that does it. For how the parts
compose and what they announce, see [Accessibility](/concepts/accessibility) and
[Announcements](/concepts/announcements).

## What to reach for next

- A second field, validation and a pending submit: [Building a form](/guides/building-a-form).
- A record count, a collection or a filter: [Components](/components) has the gallery, and the
  table family starts at [Table](/components/data-display/table).
- A shell with navigation, a sidebar or a marketing band:
  [Page compositions](/guides/page-compositions).
- Stating the spacing, the radius and the variants once for the whole product:
  [Configuration](/concepts/configuration), then [Theming](/concepts/theming).
