# Forms Cheatsheet

Quick reference for form controls and patterns in Sutra UI.

For the broader component list, use `components.cheatmd`. For design rules,
composition, and implementation constraints, see `SKILL.md`,
`skill/patterns.md`, and `usage_rules.md`.

Browse live form and component examples at [sutraui.gurujada.com](https://sutraui.gurujada.com).

## Basic Form
{: .col-2}

### LiveView Form with Changeset

```heex
<.form for={@form} class="form" phx-change="validate" phx-submit="save">
  <.input field={@form[:name]} label="Name" />
  <.input field={@form[:email]} type="email" label="Email" />
  <.button type="submit" phx-disable-with="Saving...">Save</.button>
</.form>
```

### Manual Form

```heex
<.form for={@form} phx-submit="save">
  <.input field={@form[:name]} label="Name" />
  <.button type="submit">Save</.button>
</.form>
```

## Text Inputs
{: .col-2}

### Basic Input

```heex
<.input type="text" name="name" value={@name} />
<.input type="text" name="name" placeholder="Enter name" />
<.input type="text" name="name" disabled />
```

### With Label and Description

```heex
<.input
  field={@form[:name]}
  label="Full Name"
  description="Enter your legal name"
/>
```

### Input with Errors

```heex
<.input
  id="email"
  name="email"
  value={@email}
  type="email"
  label="Email"
  errors={@errors}
/>
```

### Input Types Reference

| Type | Example |
|------|---------|
| `text` | `<.input type="text" />` |
| `email` | `<.input type="email" />` |
| `password` | `<.input type="password" />` |
| `number` | `<.input type="number" min="0" />` |
| `tel` | `<.input type="tel" />` |
| `url` | `<.input type="url" />` |
| `search` | `<.input type="search" />` |
| `date` | `<.input type="date" />` |
| `time` | `<.input type="time" />` |
| `datetime-local` | `<.input type="datetime-local" />` |

## Textarea
{: .col-2}

### Basic Textarea

```heex
<.textarea name="bio" />
<.textarea name="bio" rows="6" />
<.textarea name="bio" placeholder="Tell us about yourself..." />
```

### With Character Count

```heex
<.input
  field={@form[:bio]}
  type="textarea"
  label="Bio"
  description={"#{String.length(@bio || "")}/500 characters"}
  maxlength="500"
/>
```

## Input Groups
{: .col-2}

### With Prefix

```heex
<.input_group>
  <:prefix>https://</:prefix>
  <input type="text" name="domain" class="input pl-21" placeholder="example.com" />
</.input_group>
```

### With Suffix

```heex
<.input_group>
  <input type="number" name="weight" class="input pr-12" />
  <:suffix>kg</:suffix>
</.input_group>
```

### With Icons

```heex
<.input_group>
  <:prefix type="icon">
    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
  </:prefix>
  <input type="search" name="q" class="input pl-9" placeholder="Search..." />
</.input_group>

<.input_group>
  <:prefix>$</:prefix>
  <input type="number" name="price" class="input pl-7 pr-12" />
  <:suffix>.00</:suffix>
</.input_group>
```

## Selection Controls
{: .col-2}

### Checkbox

```heex
<.checkbox id="terms" name="terms" />

<!-- With label -->
<div class="flex items-center gap-2">
  <.checkbox id="terms" name="terms" />
  <.label for="terms">I agree to the terms</.label>
</div>

<!-- Checked by default -->
<.checkbox id="subscribe" name="subscribe" checked />
```

### Checkbox Group Pattern

```heex
<fieldset>
  <legend class="text-sm font-medium">Notifications</legend>
  <div class="space-y-2 mt-2">
    <div class="flex items-center gap-2">
      <.checkbox id="email" name="notifications[]" value="email" />
      <.label for="email">Email</.label>
    </div>
    <div class="flex items-center gap-2">
      <.checkbox id="sms" name="notifications[]" value="sms" />
      <.label for="sms">SMS</.label>
    </div>
  </div>
</fieldset>
```

### Switch

```heex
<.switch id="dark-mode" name="dark_mode" />

<!-- With label -->
<div class="flex items-center gap-2">
  <.switch id="notifications" name="notifications" />
  <.label for="notifications">Enable notifications</.label>
</div>
```

### Radio Group

```heex
<.radio_group name="plan" label="Plan">
  <:radio value="free" label="Free" checked={@selected_plan == "free"} />
  <:radio value="pro" label="Pro - $10/mo" checked={@selected_plan == "pro"} />
  <:radio
    value="enterprise"
    label="Enterprise - Contact us"
    checked={@selected_plan == "enterprise"}
  />
</.radio_group>
```

### Radio with Descriptions

```heex
<.radio_group name="shipping">
  <:radio value="standard" label="Standard">
    <span class="text-muted-foreground">3-5 business days</span>
  </:radio>
  <:radio value="express" label="Express">
    <span class="text-muted-foreground">1-2 business days</span>
  </:radio>
</.radio_group>
```

## Select Controls
{: .col-2}

### Basic Select

```heex
<.select id="country" name="country" value={@country}>
  <:trigger>{@country_label || "Select a country"}</:trigger>
  <.select_option value="us" label="United States" />
  <.select_option value="ca" label="Canada" />
  <.select_option value="mx" label="Mexico" />
</.select>
```

### Select with Groups

```heex
<.select id="timezone" name="timezone" value={@timezone} searchable>
  <:trigger>{@timezone_label || "Select timezone"}</:trigger>
  <.select_group label="Americas">
    <.select_option value="America/New_York" label="New York" />
    <.select_option value="America/Los_Angeles" label="Los Angeles" />
  </.select_group>
  <.select_separator />
  <.select_group label="Europe">
    <.select_option value="Europe/London" label="London" />
    <.select_option value="Europe/Paris" label="Paris" />
  </.select_group>
</.select>
```

### Select with Default Value

```heex
<.select id="status" name="status" value={@status} selected_label={@status_label}>
  <.select_option value="active" label="Active" />
  <.select_option value="inactive" label="Inactive" />
  <.select_option value="pending" label="Pending" />
</.select>
```

### Live Select (Async Search)

```heex
<.form for={@form} class="form" phx-change="validate">
  <.live_component
    module={SutraUI.LiveSelect}
    id="user-select"
    field={@form[:user_id]}
    placeholder="Search users..."
  />
</.form>

<%= case SutraUI.LiveSelect.decode(@form.params["user_id"]) do %>
  <% nil -> %>
  <% [] -> %>
  <% user -> %>
    Selected user id: {user}
<% end %>
```

In your LiveView:

```elixir
def handle_event("live_select_change", %{"text" => text, "id" => id}, socket) do
  users = Accounts.search_users(text)
  options = Enum.map(users, &{&1.name, &1.id})

  send_update(SutraUI.LiveSelect, id: id, options: options)
  {:noreply, socket}
end
```

## Sliders
{: .col-2}

### Basic Slider

```heex
<.slider id="volume" name="volume" min="0" max="100" value="50" />
```

### Slider with Steps

```heex
<.slider id="rating" name="rating" min="1" max="5" step="1" value="3" />
```

### Range Slider (Dual Handle)

```heex
<.range_slider
  id="price-range"
  name="price"
  min="0"
  max="1000"
  value_min="200"
  value_max="800"
/>
```

### Slider with Labels

```heex
<div class="space-y-2">
  <div class="flex items-center justify-between">
    <.label for="volume">Volume</.label>
    <output for="volume">{@volume}%</output>
  </div>
  <.slider id="volume" name="volume" min="0" max="100" value={@volume} phx-change="update_volume" />
</div>
```

## Form Patterns
{: .col-2}

### Inline Validation

```elixir
# LiveView
def handle_event("validate", %{"user" => params}, socket) do
  changeset =
    %User{}
    |> User.changeset(params)
    |> Map.put(:action, :validate)

  {:noreply, assign(socket, form: to_form(changeset))}
end
```

```heex
<.form for={@form} class="form" phx-change="validate" phx-submit="save">
  <.input field={@form[:email]} type="email" label="Email" />
  <.button type="submit">Save</.button>
</.form>
```

### Form with Loading State

```heex
<.form for={@form} class="form" phx-submit="save">
  <.input field={@form[:name]} label="Name" />
  <.button type="submit" loading={@saving} phx-disable-with="Saving...">Save</.button>
</.form>
```

### Multi-Step Form

```heex
<.form for={@form} class="form" phx-change="validate" phx-submit="next_step">
  <.stepper_wizard id="profile-wizard" current={@step} errors={@step_errors}>
    <:step id="profile" label="Profile">
      <.input field={@form[:name]} label="Name" />
    </:step>
    <:step id="details" label="Details">
      <.input field={@form[:email]} type="email" label="Email" />
    </:step>
    <:step id="review" label="Review">
      Review your details before submitting.
    </:step>

    <:actions>
      <.button type="button" variant="outline" phx-click="previous_step">Back</.button>
      <.button type="submit">Continue</.button>
    </:actions>
  </.stepper_wizard>
</.form>
```

## Filter Forms
{: .col-2}

### Filter Bar

```heex
<.filter_bar on_change="filter" on_clear="reset_filters" show_clear={@has_filters}>
  <:filter>
    <.input type="search" name="q" value={@query} placeholder="Search..." />
  </:filter>
  <:filter>
    <.input
      type="select"
      name="status"
      value={@status}
      options={[{"All", ""}, {"Active", "active"}, {"Inactive", "inactive"}]}
    />
  </:filter>
  <:filter>
    <.input
      type="select"
      name="sort"
      value={@sort}
      options={[{"Newest", "newest"}, {"Oldest", "oldest"}, {"Name", "name"}]}
    />
  </:filter>
</.filter_bar>
```

### Date Range Filter

```heex
<.filter_bar on_change="filter_dates" cols={2}>
  <:filter>
    <.input type="date" name="start_date" value={@start_date} />
  </:filter>
  <:filter>
    <.input type="date" name="end_date" value={@end_date} />
  </:filter>
</.filter_bar>
```

## Error Handling
{: .col-2}

### Input with Error

```heex
<.input
  field={@form[:email]}
  type="email"
  label="Email"
  phx-debounce="blur"
/>
```

### Form-Level Errors

```heex
<.form for={@form} class="form" phx-submit="save">
  <.alert :if={@form.errors[:base]} variant="destructive">
    <:title>{translate_error(@form.errors[:base])}</:title>
  </.alert>

  <.input field={@form[:email]} label="Email" />
  <.button type="submit">Save</.button>
</.form>
```

### Async Validation

```elixir
def handle_event("validate_email", %{"email" => email}, socket) do
  case Accounts.email_available?(email) do
    true -> {:noreply, assign(socket, email_error: nil)}
    false -> {:noreply, assign(socket, email_error: "Email already taken")}
  end
end
```

```heex
<.input
  id="email"
  name="email"
  value={@email}
  type="email"
  label="Email"
  phx-blur="validate_email"
  phx-debounce="500"
  errors={if @email_error, do: [@email_error], else: []}
/>
```
