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, 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).
Example
Stack each label and field in a div with mb-3:
<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.
<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.
<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.
<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:
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 lists its props.
Accessibility
- Give every field a label connected with
htmlForandid, in horizontal forms too. - Hide a label with
visually-hiddeninstead 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 takescol-form-label. .input-group-textdoes not label the field. Add a label oraria-label.
Related
- React Bootstrap form layout: the same layouts in AdminKit for React (Vite)
- Bootstrap 5 form layout and input group: the layouts of the HTML version
- Form controls: inputs, textareas and submitting a form
- Form Layouts demo and Input Groups demo
Last updated on
Validation
Next.js Bootstrap form validation: the browser's own checks with Bootstrap feedback in a client component, and errors from your API shown with is-invalid.
Data tablePro
Next.js DataTables for the App Router: a client-only DataTable loaded with next/dynamic, rendered from server component pages, with Bootstrap 5 styling.