Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Compact Dense Density Form Block for React

A new-task form with a density switch: Comfortable, Compact and Dense change the control height, the gaps and the type through CSS variables, with the numbers printed, and a phone keeps 40 pixel controls.

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

Let people choose how much form fits on the screen with this density form for React with Base UI. A compact header holds a Toggle Group of Comfortable, Compact and Dense over a hairline frame with a plus mark on each corner. The form is a new task for a household to-do app: a name (Input), an assignee and a priority (Selects), a due date (Date Input), tags and a "Notify the assignee" Checkbox. The density sets three CSS variables on the panel, the control height (40, 32 or 28 px), the gap between fields and the type size, so one form is restyled without rebuilding it, and a line in the first band prints the numbers ("32 px controls, 16 px gaps"). Dense stays above the 24 px touch-target floor. On a phone the controls keep 40 px whatever is chosen and Dense is not offered. Add task checks the name, the assignee and the due date, names the first problem in the foot and focuses it. The Shine Button is the only colored control. It's built from Toggle Group, Input, Select, Date Input, Checkbox, Shine Button and Guides, with a CSS entrance only. For admin tools, data entry, internal apps and any form that people fill in all day.

FAQ

How does the density switch work?

lookOf returns one string of three variables for the chosen density: --ctl (control height), --gap (space between fields) and --fs (type size). The panel carries that string, and every control reads the variables (sm:h-(--ctl), sm:gap-(--gap)), so nothing is re-rendered with a different component. To add a density, add a branch to lookOf, its numbers to heightOf and gapOf, and an entry to DENSITIES.

Is Dense too small to use?

At 28 px it is above the 24 px minimum target size, and each control has space around it from the gap. It is meant for a pointer. Below the small breakpoint the height variable is ignored: every control is 40 px, the Dense choice is hidden and the line prints "40 px controls on a phone".

When are problems shown?

On Add task, for the name, the assignee and the due date at once. The first one is worded in the status line and its control is focused; the fields have no line of their own, so the form can stay as tight as the density asks. After that the status follows what is still wrong, and a field marked invalid gets the destructive border.

How does the density group work with a keyboard?

The three choices are one group that is named "Density", always with exactly one pressed; Tab enters it and the arrow keys move between them, and choosing one changes the form but never moves focus. The header and the first band do not move when the form tightens, only the panel below them.

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

Nothing is created: Add task says Added "Take out the recycling" in this demo. Nothing was sent. To make it real, post the draft from the submit handler, validate it again on the server and keep the person's chosen density in a setting of your own (a cookie or their profile), reading it into the initial state.

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

With react-hook-form, use Controller for the selects and the date input, and move problemsOf into a resolver; the density is not part of the form, so keep it in its own state.