Input Group

Renders a group of related inputs with a shared label, help text, and errors.

Read more Read less

This component is used when two or more inputs belong together logically — for example, a budget range (min/max), a date range, or any pair of related values that share a single label.

Child inputs should set label_hidden={true} so that only the group label is visible. The group label replaces individual input labels.

Examples

<.input_group id="budget-range" label="Budget per person">
  <:help>Includes taxes</:help>
  <.input type="money" field={@form[:budget_min]} placeholder="Min" label_hidden />
  <.input type="money" field={@form[:budget_max]} placeholder="Max" label_hidden />
</.input_group>

<.input_group id="age-range" label="Age range" errors={["Enter both values"]}>
  <.input type="number" field={@form[:age_min]} placeholder="Min" label_hidden />
  <.input type="number" field={@form[:age_max]} placeholder="Max" label_hidden />
</.input_group>

Repeated rows

When the group is one row of a repeatable list, add a :remove slot for an icon-only remove button after the inputs. Set align="start" when a child input shows help or error text below it, so the inputs and the button stay aligned along their top edge instead of drifting with the text.

<.input_group id="phone-0" label="Phone number" align="start">
  <:remove label="Remove phone number" on_click={JS.push("remove_phone", value: %{index: 0})} />
  <.input type="dropdown" field={phone[:type]} options={@types} label_hidden fit_content />
  <.input type="phone" field={phone[:number]} label_hidden />
</.input_group>
–

Includes taxes

<div id="input-group-single-money-range-storybook-wrapper" style="width: 100%;">
  <form>
    <.input_group id="input-group-single-money-range" label="Budget per person">
      <:help>Includes taxes</:help>
      <.stack space="none">
        <.input type="money" name="budget_min" value="" label="Minimum" placeholder="Min" label_hidden={true} />
      </.stack>
      <span>&ndash;</span>
      <.stack space="none">
        <.input type="money" name="budget_max" value="" label="Maximum" placeholder="Max" label_hidden={true} />
      </.stack>
    </.input_group>
  </form>
</div>
–

Includes taxes

<div id="input-group-single-money-range-with-values-storybook-wrapper" style="width: 100%;">
  <form>
    <.input_group id="input-group-single-money-range-with-values" label="Budget per room">
      <:help>Includes taxes</:help>
      <.stack space="none">
        <.input type="money" name="budget_min" value="100" label="Minimum" placeholder="Min" label_hidden={true} />
      </.stack>
      <span>&ndash;</span>
      <.stack space="none">
        <.input type="money" name="budget_max" value="250" label="Maximum" placeholder="Max" label_hidden={true} />
      </.stack>
    </.input_group>
  </form>
</div>
–

Enter both a minimum and a maximum

<div id="input-group-single-money-range-with-error-storybook-wrapper" style="width: 100%;">
  <form>
    <.input_group
      id="input-group-single-money-range-with-error"
      label="Budget per person"
      required
      errors={["Enter both a minimum and a maximum"]}
    >
      <:help>Includes taxes</:help>
      <.stack space="none">
        <.input type="money" name="budget_min" value="" label="Minimum" placeholder="Min" label_hidden={true} errors={["can't be blank"]} show_error_messages={false} />
      </.stack>
      <span>&ndash;</span>
      <.stack space="none">
        <.input type="money" name="budget_max" value="" label="Maximum" placeholder="Max" label_hidden={true} errors={["can't be blank"]} show_error_messages={false} />
      </.stack>
    </.input_group>
  </form>
</div>
–
<div id="input-group-single-number-range-storybook-wrapper" style="width: 100%;">
  <form>
    <.input_group id="input-group-single-number-range" label="Age range">
      <.stack space="none">
        <.input type="number" name="age_min" value="" label="Minimum age" placeholder="Min" label_hidden={true} />
      </.stack>
      <span>&ndash;</span>
      <.stack space="none">
        <.input type="number" name="age_max" value="" label="Maximum age" placeholder="Max" label_hidden={true} />
      </.stack>
    </.input_group>
  </form>
</div>
–

Includes taxes

<div id="input-group-single-required-storybook-wrapper" style="width: 100%;">
  <form>
    <.input_group id="input-group-single-required" label="Budget per person" required>
      <:help>Includes taxes</:help>
      <.stack space="none">
        <.input type="money" name="budget_min" value="" label="Minimum" placeholder="Min" label_hidden={true} />
      </.stack>
      <span>&ndash;</span>
      <.stack space="none">
        <.input type="money" name="budget_max" value="" label="Maximum" placeholder="Max" label_hidden={true} />
      </.stack>
    </.input_group>
  </form>
</div>

For extensions, use x or #, e.g. (505) 923 1234x202

<div id="input-group-single-repeated-row-storybook-wrapper" style="width: 100%;">
  <form>
    <.input_group id="input-group-single-repeated-row" label="Phone number" align="start">
      <:remove label="Remove phone number" on_click={JS.push("remove_phone", value: %{index: 0})} />
      <:help>For extensions, use x or #, e.g. (505) 923 1234x202</:help>
      <.input type="dropdown" id="phone-number-0-type" name="phone[type]" value="work" label="Phone type" label_hidden={true} fit_content={true} options={[%{label: "Work", value: "work"}, %{label: "Mobile", value: "mobile"}, %{label: "Fax", value: "fax"}]} />
      <.input type="phone" id="phone-number-0-number" name="phone[number]" value="" label="Phone number" label_hidden={true} show_default_help={false} />
    </.input_group>
  </form>
</div>