Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Settings Drawer Form Block for React

A longer settings form in a bottom drawer with a fixed header and footer, soft shadows at the scrolling edges, a count of unsaved changes worked out from the fields and a guard against closing a dirty form.

Framework
UI library

Preview

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Overview

Edit a company in a drawer without losing your place with this drawer form for React with Base UI. A compact header holds the page title and the real trigger, an outline Button, over a drawing of the company page. The settings are shown open in place on load, docked to the foot of that page, and pressing Company settings opens the real Drawer with the same state. The body holds five fields: name, a website that is checked as you leave it, an industry, a description with a 500 character count that appears in the last 50, and a public listing Switch. The header and footer stay put while the body scrolls between them, with a soft shadow at whichever edge has more to scroll to. The footer says "2 unsaved changes", worked out from the fields, beside Discard changes and Save changes. Closing a dirty form by Escape, the swipe, the scrim or the close button swaps the footer for "Close without saving 2 changes?" with Keep editing and Close anyway. Save checks the fields, closes the drawer and puts the new name on the page behind. It's built from Drawer, Field, Input, Textarea, Switch, Shine Button and Guides, with a CSS entrance only. For settings, profile and record editing that is longer than a dialog.

FAQ

How do I change the fields, the limits and the saved company?

Edit SAVED for the company the page opens with, NAME_MIN, NAME_MAX and DESCRIPTION_MAX for the limits, and INDUSTRIES for the select. problemsOf is the one check for every field and changesOf counts how many differ from SAVED, so a new field is a line in each plus its row in CompanyFields. The drawn page behind reads the saved values, so it follows a save.

How does the unsaved count work, and what does closing do?

changesOf compares each field with what was saved, so editing a field and typing it back makes it clean again. With nothing changed every way out closes at once. With something changed Escape, the swipe, the scrim and the close button each ask first in the footer's own space, so the drawer never changes height, and focus lands on Keep editing; a second Escape means keep editing. Discard changes puts every field back and stays open.

How are the shadows at the edges of the body made?

With CSS only: four gradients on the scrolling body, two of them attached to the content (background-attachment: local) in the drawer's own colour, so they cover the shadows at the very top and the very bottom. A shadow therefore shows only at an edge with more to scroll to, without a scroll listener. The body is a normal scrolling region, so the keyboard and a screen reader scroll it as usual.

Why a bottom drawer, and how does it work on a phone?

A bottom drawer is the same shape on every screen, grows upward with the form and takes the swipe-down gesture phones expect; its form sits in a column no wider than 32rem. On a phone the in-place copy docks to the foot of the page and the footer's two buttons share a row. The real drawer keeps the catalog's behaviour: focus trapped inside, Escape or a swipe to close, focus back on the trigger.

What stays local, and how do I connect it for real?

Nothing is saved: Save changes says "Saved Ottersham Textiles in this demo. Nothing was sent." and the page behind is the block's own state. To make it real, send the values from the submit handler, check them on your server too, and keep the drawer open with the server's message in the status line when it refuses. Replace the typed SAVED with the company your server returns.

How do I connect it to react-hook-form or TanStack Form?

Use the form library's dirty state in place of changesOf. With react-hook-form, formState.dirtyFields gives the count, reset is Discard changes, and handleSubmit replaces the submit handler; keep the close guard on isDirty. With TanStack Form, form.state.isDirty drives the guard and form.reset() is Discard changes.

  • Pro

    Modal Create Form

    Dialogs and layouts

    A project manager's Projects page with its New project dialog shown open: name, description and category, and a discard guard that swaps the footer for Keep editing and Discard when a dirty form is dismissed.

  • Pro

    Unsaved Changes Dialog

    Confirm and consent

    An essay editor with its leave prompt shown open: an alert dialog that lists what changed since the last save and offers Save and leave, Discard changes and Keep editing.

  • Pro

    App Rating Dialog

    Drawers and popovers

    A water tracker's Today screen with its rating drawer shown open over a drawn phone: five stars with a word each, then a branch, where four or five stars offers the store and one to three asks what could be better, with topics and a note.

  • Pro

    A stand-up update composer whose character counter is a ring that fills to 280, shows its number only near the end, goes negative past the limit and is spoken at four stages, with a list of the latest two posts.

  • Pro

    Review and Submit Form

    Dialogs and layouts

    A review step: three sections of typed answers that each edit in place in the same boxes, a side cell of what changed, a Submit that names the section to finish, and a lock after a local submit.

  • Pro

    A listing editor in a fixed-height window with a section index that follows the scroll, statuses worked out from the fields, and one Save listing action in the top bar.