# Next.js Bootstrap Form Layout

Next.js Bootstrap form layout for the App Router: grid, horizontal and inline forms and input groups as server-rendered markup, plus a client Dropdown.

The layout decides how a form reads: fields stacked, side by side, or with the labels to the left. In [AdminKit for Next.js](/nextjs/), you build it with Bootstrap's grid and spacing classes in JSX. This works the same in the free and PRO versions.

In the App Router, pages are server components: they render on the server and have no state. Layout markup has no state either, so it renders in a server component page with no `"use client"` needed. The PRO demo's Form Layouts and Input Groups pages are both server components.

Only the parts with behavior are client components, files that start with `"use client"`. One is the `Dropdown` of an input group (below). The other is the submit handler of a form. You write it in a client component because a static export has no Server Actions (see [Form controls](/nextjs/docs/forms/form-control/#submitting-a-form)).

## Example

Stack each label and field in a `div` with `mb-3`:

```html
<form>
  <div class="mb-3">
    <label class="form-label" for="layoutEmail">Email address</label>
    <input type="email" class="form-control" id="layoutEmail" placeholder="Email">
  </div>
  <div class="mb-3">
    <label class="form-label" for="layoutPassword">Password</label>
    <input type="password" class="form-control" id="layoutPassword" placeholder="Password">
  </div>
  <div class="mb-3">
    <label class="form-check m-0">
      <input type="checkbox" class="form-check-input">
      <span class="form-check-label">Check me out</span>
    </label>
  </div>
  <button type="submit" class="btn btn-primary">Submit</button>
</form>
```
```tsx code
<form>
  <div className="mb-3">
    <label className="form-label" htmlFor="layoutEmail">Email address</label>
    <input type="email" className="form-control" id="layoutEmail" placeholder="Email" />
  </div>
  <div className="mb-3">
    <label className="form-label" htmlFor="layoutPassword">Password</label>
    <input type="password" className="form-control" id="layoutPassword" placeholder="Password" />
  </div>
  <div className="mb-3">
    <label className="form-check m-0">
      <input type="checkbox" className="form-check-input" />
      <span className="form-check-label">Check me out</span>
    </label>
  </div>
  <button type="submit" className="btn btn-primary">Submit</button>
</form>
```

## Form grid

Fields that belong together can share a row. A `row` with `col-md-*` columns puts them next to each other from `md` up, and `g-3` adds the gutters.

```html
<form class="row g-3">
  <div class="col-md-6">
    <label class="form-label" for="gridCity">City</label>
    <input type="text" class="form-control" id="gridCity">
  </div>
  <div class="col-md-4">
    <label class="form-label" for="gridState">State</label>
    <select class="form-select" id="gridState">
      <option selected>Choose...</option>
      <option>California</option>
    </select>
  </div>
  <div class="col-md-2">
    <label class="form-label" for="gridZip">Zip</label>
    <input type="text" class="form-control" id="gridZip">
  </div>
</form>
```
```tsx code
<form className="row g-3">
  <div className="col-md-6">
    <label className="form-label" htmlFor="gridCity">City</label>
    <input type="text" className="form-control" id="gridCity" />
  </div>
  <div className="col-md-4">
    <label className="form-label" htmlFor="gridState">State</label>
    <select className="form-select" id="gridState" defaultValue="Choose...">
      <option>Choose...</option>
      <option>California</option>
    </select>
  </div>
  <div className="col-md-2">
    <label className="form-label" htmlFor="gridZip">Zip</label>
    <input type="text" className="form-control" id="gridZip" />
  </div>
</form>
```

## Horizontal form

For labels to the left of their fields, make every field a `row`. The label gets `col-form-label col-sm-2 text-sm-end`, and `col-sm-10` goes around the field.

```html
<form>
  <div class="mb-3 row">
    <label class="col-form-label col-sm-2 text-sm-end" for="horizontalEmail">Email</label>
    <div class="col-sm-10">
      <input type="email" class="form-control" id="horizontalEmail" placeholder="Email">
    </div>
  </div>
  <div class="mb-3 row">
    <div class="col-sm-10 ms-sm-auto">
      <button type="submit" class="btn btn-primary">Submit</button>
    </div>
  </div>
</form>
```
```tsx code
<form>
  <div className="mb-3 row">
    <label className="col-form-label col-sm-2 text-sm-end" htmlFor="horizontalEmail">Email</label>
    <div className="col-sm-10">
      <input type="email" className="form-control" id="horizontalEmail" placeholder="Email" />
    </div>
  </div>
  <div className="mb-3 row">
    <div className="col-sm-10 ms-sm-auto">
      <button type="submit" className="btn btn-primary">Submit</button>
    </div>
  </div>
</form>
```

## Inline form

To fit a few fields on a single line, use `row-cols-md-auto`. Each column is then as wide as its content, so the fields share a line from `md` up. The labels stay in the markup with `visually-hidden`.

```html
<form class="row row-cols-md-auto align-items-center">
  <div class="col-12">
    <label class="visually-hidden" for="inlineName">Name</label>
    <input type="text" class="form-control mb-2 me-sm-2" id="inlineName" placeholder="Jane Doe">
  </div>
  <div class="col-12">
    <label class="visually-hidden" for="inlineUsername">Username</label>
    <div class="input-group mb-2 me-sm-2">
      <div class="input-group-text">@</div>
      <input type="text" class="form-control" id="inlineUsername" placeholder="Username">
    </div>
  </div>
  <div class="col-12">
    <button type="submit" class="btn btn-primary mb-2">Submit</button>
  </div>
</form>
```
```tsx code
<form className="row row-cols-md-auto align-items-center">
  <div className="col-12">
    <label className="visually-hidden" htmlFor="inlineName">Name</label>
    <input type="text" className="form-control mb-2 me-sm-2" id="inlineName" placeholder="Jane Doe" />
  </div>
  <div className="col-12">
    <label className="visually-hidden" htmlFor="inlineUsername">Username</label>
    <div className="input-group mb-2 me-sm-2">
      <div className="input-group-text">@</div>
      <input type="text" className="form-control" id="inlineUsername" placeholder="Username" />
    </div>
  </div>
  <div className="col-12">
    <button type="submit" className="btn btn-primary mb-2">Submit</button>
  </div>
</form>
```

## Input groups

When a field needs a unit, a prefix or a button at its side, use an input group. Put the field in an `.input-group`, next to `.input-group-text` or buttons. `input-group-lg` and `input-group-sm` resize it. For a dropdown button in the group, use the `Dropdown` client component with `className="input-group"`. The server component page can render it directly, because its children are plain markup:

```html
<div class="input-group mb-3">
  <span class="input-group-text">$</span>
  <input type="text" class="form-control" aria-label="Amount (to the nearest dollar)">
  <span class="input-group-text">.00</span>
</div>
<div class="input-group">
  <button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Action</button>
  <div class="dropdown-menu">
    <a class="dropdown-item" href="#">Edit</a>
    <a class="dropdown-item" href="#">Archive</a>
  </div>
  <input type="text" class="form-control" aria-label="Text input with dropdown button">
</div>
```
```tsx code title="src/app/(dashboard)/orders/page.tsx"
import { Dropdown, DropdownMenu, DropdownToggle } from "@/components/bootstrap/dropdown";
import { pageMetadata } from "@/lib/site";

export const metadata = pageMetadata("/orders");

export default function OrdersPage() {
  return (
    <>
      <h1 className="h3 mb-3">Orders</h1>
      <div className="input-group mb-3">
        <span className="input-group-text">$</span>
        <input type="text" className="form-control" aria-label="Amount (to the nearest dollar)" />
        <span className="input-group-text">.00</span>
      </div>
      <Dropdown className="input-group">
        <DropdownToggle as="button" className="btn btn-secondary dropdown-toggle">
          Action
        </DropdownToggle>
        <DropdownMenu>
          <a className="dropdown-item" href="#">Edit</a>
          <a className="dropdown-item" href="#">Archive</a>
        </DropdownMenu>
        <input type="text" className="form-control" aria-label="Text input with dropdown button" />
      </Dropdown>
    </>
  );
}
```

List `/orders` in `src/data/pages.json`, where `pageMetadata()` reads the page's title and description. `DropdownToggle` with `as="button"` renders `type="button"`, so it never submits the form. [Dropdowns](/nextjs/docs/components/dropdowns/) lists its props.

## Accessibility

- Give every field a label connected with `htmlFor` and `id`, in horizontal forms too.
- Hide a label with `visually-hidden` instead of removing it, as the inline form does.
- Wrap a group of radios or checkboxes in a `<fieldset>` with a `<legend>`. In a horizontal form, the legend takes `col-form-label`.
- `.input-group-text` does not label the field. Add a label or `aria-label`.

## Related

- [React Bootstrap form layout](/react/docs/forms/layout/): the same layouts in AdminKit for React (Vite)
- [Bootstrap 5 form layout](/bootstrap/docs/forms/layout/) and [input group](/bootstrap/docs/forms/input-group/): the layouts of the HTML version
- [Form controls](/nextjs/docs/forms/form-control/): inputs, textareas and submitting a form
- [Form Layouts demo](https://nextjs-demo.adminkit.io/forms-layouts) and [Input Groups demo](https://nextjs-demo.adminkit.io/forms-input-groups)

---

Canonical HTML page: https://adminkit.io/nextjs/docs/forms/layout/

Site overview for agents: https://adminkit.io/llms.txt (expanded: https://adminkit.io/llms-full.txt) · Documentation: https://adminkit.io/bootstrap/docs/ · Questions: support@adminkit.io
