Density Provider
StableDensity is a contract, not a preference. The same component serves a patient reading on a phone and a nurse scanning ninety rows, and the difference is spacing and target size — never which clinical facts appear. Hiding a fact to save a row is a defect, not a density mode. The provider also clamps to the WCAG 2.2 target-size floor rather than trusting every component to remember, and nests so a patient-facing card inside a clinical worklist keeps its own density.
pnpm dlx shadcn@latest add @oxygenui/density-providerFirst install? Add "@oxygenui": "https://oxygenui.design/r/{name}.json" to the registries block of your components.json first — or skip the config and pass https://oxygenui.design/r/density-provider.json directly.
Preview
Every state, switchable.
patient
1 critical result in this panel.
| Test | Result | Reference | Interpretation |
|---|---|---|---|
Potassium | 6.8 mmol/L | 3.5 – 5.1 mmol/L | Critical high |
Blood pressure | 2 parts | High | |
| Systolic | 168 mmHg | 90 – 130 mmHg | High |
| Diastolic | 82 mmHg | 60 – 85 mmHg | Normal |
Hemoglobin | 10.2 g/dL | 12 – 15.5 g/dL | Low |
standard
1 critical result in this panel.
| Test | Result | Reference | Interpretation |
|---|---|---|---|
Potassium | 6.8 mmol/L | 3.5 – 5.1 mmol/L | Critical high |
Blood pressure | 2 parts | High | |
| Systolic | 168 mmHg | 90 – 130 mmHg | High |
| Diastolic | 82 mmHg | 60 – 85 mmHg | Normal |
Hemoglobin | 10.2 g/dL | 12 – 15.5 g/dL | Low |
clinical
1 critical result in this panel.
| Test | Result | Reference | Interpretation |
|---|---|---|---|
Potassium | 6.8 mmol/L | 3.5 – 5.1 mmol/L | Critical high |
Blood pressure | 2 parts | High | |
| Systolic | 168 mmHg | 90 – 130 mmHg | High |
| Diastolic | 82 mmHg | 60 – 85 mmHg | Normal |
Hemoglobin | 10.2 g/dL | 12 – 15.5 g/dL | Low |
Spacing and target size change. Which clinical facts appear does not — hiding a fact to save a row is a defect.
Usage
Props are the FHIR resource.
import { DensityProvider, useDensity } from "@/components/oxygen/density-provider";
<DensityProvider density="clinical">
<ObservationPanel observations={observations} />
</DensityProvider>
// Components can adapt behaviour, not just spacing.
const density = useDensity();Dependencies
- lucide-react
- clsx
- tailwind-merge
| Prop | Type | Default |
|---|---|---|
| children | React.ReactNode | — |
| density | Density | — |
| asChild Render without a wrapping element by cloning the single child. Use inside table rows and other places where an extra div would break layout. | boolean | undefined | false |
| className | string | undefined | — |
Guidance
When to use it, and when not to.
DECISION SURFACE / 6 RULES
Recommended context
Use it
- At the root of a worklist, flowsheet, or patient-facing surface.
- Nested, when a patient-facing card sits inside a clinical screen.
- With asChild inside table rows and other layout-sensitive containers.
Guardrails
Don't
- Deriving density from the viewport. Density and breakpoint are independent axes.
- Using clinical density to fit more facts by hiding some. That is a defect.
- Assuming the floor makes any spacing safe \u2014 it clamps targets, not text size.
Quality
What was tested, and what is still missing.
- Target-size floor
- Clinical density cannot shrink an interactive target below the WCAG 2.2 minimum of 24 CSS pixels. DensityTarget enforces it around any control.
- Independent of zoom
- Density is an author decision; user text scaling and zoom apply on top and are not overridden.
- No content hidden
- Spacing changes only. Nothing that is visible at patient density disappears at clinical density.
Open gaps
Known limitations
- Does not read OS accessibility preferences \u2014 wire those into the density you pass.
- The floor applies to controls wrapped in DensityTarget, not automatically to every descendant.
- Token values come from @oxygenui-design/tokens; this sets the attribute and context, not the spacing scale.
Source
Exactly what lands in your repository.
Read directly from the published registry, so this can never drift from what the CLI installs.
Show sourceHide source122 lines
"use client";
/**
* DensityProvider — sets the spacing and target-size contract for a subtree.
*
* Density is a contract, not a preference. The same component serves a patient
* reading on a phone and a nurse scanning ninety rows, and the difference
* between those is spacing and target size — never which clinical facts appear.
* Hiding a fact to save a row is a defect, not a density mode.
*
* Two things this enforces that a plain CSS variable cannot:
*
* 1. A WCAG 2.2 target-size floor. `clinical` density is allowed to be
* tight, but it is not allowed to shrink an interactive target below the
* 24px minimum. The provider clamps rather than trusting every component
* to remember.
* 2. Nesting. A patient-facing card inside a clinical worklist keeps its own
* density, because innermost wins.
*
* Density and breakpoint are independent axes. A clinical worklist stays
* clinical on a tablet at the bedside; it does not become patient density
* because the viewport got narrower.
*/
import * as React from "react";
export type Density = "patient" | "standard" | "clinical";
interface DensityContextValue {
density: Density;
/** True when the value came from a provider rather than the root default. */
explicit: boolean;
}
const DensityContext = React.createContext<DensityContextValue>({
density: "standard",
explicit: false,
});
/** Read the density in force. Components adapt behaviour, not only spacing. */
export function useDensity(): Density {
return React.useContext(DensityContext).density;
}
/**
* WCAG 2.2 AA target size (minimum) is 24×24 CSS pixels. Clinical density is
* dense by design, but this is the floor it may not cross.
*/
const MIN_TARGET_PX = 24;
export interface DensityProviderProps {
density: Density;
/**
* Render without a wrapping element by cloning the single child. Use inside
* table rows and other places where an extra div would break layout.
*/
asChild?: boolean;
className?: string;
children: React.ReactNode;
}
export function DensityProvider({
density,
asChild = false,
className,
children,
}: DensityProviderProps) {
const value = React.useMemo<DensityContextValue>(() => ({ density, explicit: true }), [density]);
// The attribute is what the token file keys off; the context is what
// components read when spacing alone is not enough.
const props = {
"data-ox-density": density,
style: { "--ox-density-target-floor": `${MIN_TARGET_PX}px` } as React.CSSProperties,
className,
};
if (asChild && React.isValidElement(children)) {
return (
<DensityContext.Provider value={value}>
{React.cloneElement(children as React.ReactElement<Record<string, unknown>>, props)}
</DensityContext.Provider>
);
}
return (
<DensityContext.Provider value={value}>
<div {...props}>{children}</div>
</DensityContext.Provider>
);
}
/**
* A control that honours the density floor.
*
* Wraps any interactive element so that clinical density can tighten its
* spacing without taking the hit area below the accessible minimum. The visual
* box may be small; the touch target is not.
*/
export function DensityTarget({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<span
className={className}
style={{
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
minWidth: `max(${MIN_TARGET_PX}px, var(--ox-density-target, ${MIN_TARGET_PX}px))`,
minHeight: `max(${MIN_TARGET_PX}px, var(--ox-density-target, ${MIN_TARGET_PX}px))`,
}}
>
{children}
</span>
);
}