Skip to content
All components

Density Provider

Stable

Primitive · System

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 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-provider

First 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.

Live · synthetic data

patient

1 critical result in this panel.

Observations
TestResultReferenceInterpretation
Potassium
6.8 mmol/L3.5 – 5.1 mmol/LCritical high
Blood pressure
2 partsHigh
Systolic168 mmHg90 – 130 mmHgHigh
Diastolic82 mmHg60 – 85 mmHgNormal
Hemoglobin
10.2 g/dL12 – 15.5 g/dLLow

standard

1 critical result in this panel.

Observations
TestResultReferenceInterpretation
Potassium
6.8 mmol/L3.5 – 5.1 mmol/LCritical high
Blood pressure
2 partsHigh
Systolic168 mmHg90 – 130 mmHgHigh
Diastolic82 mmHg60 – 85 mmHgNormal
Hemoglobin
10.2 g/dL12 – 15.5 g/dLLow

clinical

1 critical result in this panel.

Observations
TestResultReferenceInterpretation
Potassium
6.8 mmol/L3.5 – 5.1 mmol/LCritical high
Blood pressure
2 partsHigh
Systolic168 mmHg90 – 130 mmHgHigh
Diastolic82 mmHg60 – 85 mmHgNormal
Hemoglobin
10.2 g/dL12 – 15.5 g/dLLow

Spacing and target size change. Which clinical facts appear does not — hiding a fact to save a row is a defect.

Patient densityStandard densityClinical densityNested providersTarget-size floor enforced

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
Density Provider props
PropTypeDefault
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 | undefinedfalse
className

string | undefined

Guidance

When to use it, and when not to.

DECISION SURFACE / 6 RULES

Recommended context

Use it

03
  • 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

03
  • 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

03
  • 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 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>
  );
}