AdvancedBeta

Density CSS attributes are shared CSS APIs for color, depth, border, radius, shadow, emphasis, size, typography, and interaction. Use them directly on HTML elements to make custom interface pieces feel consistent with Density components.

Color

Density handles most color for you, including

  • UI colors for controls, status, and text
  • Data visualization colors for charts

Use data-dn-color for semantic UI status colors. It accepts destructive, warning, and success, and exposes matching background, border, and foreground variables to HTML elements.

Customize the underlying status color variables on the configuration page.

BlockedNeeds reviewLive
Usage is near the project limit.
Healthy
Errors: 18
Preview published
export default function StatusColorExample() {
  return (
    <div style={{ display: "grid", gap: "0.75rem" }}>
      <div style={{ display: "flex", flexWrap: "wrap", gap: "0.5rem" }}>
        <span data-dn-color="destructive">Blocked</span>
        <span data-dn-color="warning">Needs review</span>
        <span data-dn-color="success">Live</span>
      </div>
      <div data-dn-color="warning">Usage is near the project limit.</div>
      <div style={{ display: "flex", flexWrap: "wrap", gap: "0.5rem" }}>
        <button data-dn-clickable data-dn-color="destructive" type="button">
          Delete
        </button>
        <button data-dn-clickable data-dn-color="warning" type="button">
          Pause
        </button>
        <button data-dn-clickable data-dn-color="success" type="button">
          Publish
        </button>
      </div>
      <div style={{ display: "flex", flexWrap: "wrap", gap: "1rem" }}>
        <div data-dn-color="success">Healthy</div>
        <div data-dn-color="destructive">Errors: 18</div>
      </div>
      <div data-dn-color="success">Preview published</div>
    </div>
  );
}

Use the ordered --dn-color-chart-series-1 through --dn-color-chart-series-6 variables for categorical series. Assign them consistently so a category keeps its color across related charts.

Use --dn-color-chart-positive and --dn-color-chart-negative for signed or semantic values, such as gains and losses. They describe the value’s meaning rather than its series position.

--dn-color-chart-axis, --dn-color-chart-grid, and --dn-color-chart-label style chart structure and labels without competing with the data.

Categorical series 1--dn-color-chart-series-1
Categorical series 2--dn-color-chart-series-2
Categorical series 3--dn-color-chart-series-3
Categorical series 4--dn-color-chart-series-4
Categorical series 5--dn-color-chart-series-5
Categorical series 6--dn-color-chart-series-6
Positive value--dn-color-chart-positive
Negative value--dn-color-chart-negative
Axis--dn-color-chart-axis
Grid--dn-color-chart-grid
Label--dn-color-chart-label
const chartColors = [
  {
    description: "Categorical series 1",
    token: "--dn-color-chart-series-1",
  },
  {
    description: "Categorical series 2",
    token: "--dn-color-chart-series-2",
  },
  {
    description: "Categorical series 3",
    token: "--dn-color-chart-series-3",
  },
  {
    description: "Categorical series 4",
    token: "--dn-color-chart-series-4",
  },
  {
    description: "Categorical series 5",
    token: "--dn-color-chart-series-5",
  },
  {
    description: "Categorical series 6",
    token: "--dn-color-chart-series-6",
  },
  {
    description: "Positive value",
    token: "--dn-color-chart-positive",
  },
  {
    description: "Negative value",
    token: "--dn-color-chart-negative",
  },
  {
    description: "Axis",
    token: "--dn-color-chart-axis",
  },
  {
    description: "Grid",
    token: "--dn-color-chart-grid",
  },
  {
    description: "Label",
    token: "--dn-color-chart-label",
  },
] as const;

export default function ChartColorExample() {
  return (
    <div
      aria-label="Chart color tokens"
      style={{ display: "grid", gap: "0.5rem" }}
    >
      {chartColors.map(({ description, token }) => (
        <div
          key={token}
          style={{
            alignItems: "center",
            backgroundColor: "var(--dn-color-background)",
            border: "1px solid var(--dn-color-border)",
            display: "flex",
            gap: "0.5rem",
            minWidth: 0,
            padding: "0.75rem",
            width: "100%",
          }}
        >
          <span
            aria-label={`${description}: ${token}`}
            style={{
              backgroundColor: `var(${token})`,
              flex: "0 0 auto",
              height: "28px",
              width: "28px",
            }}
          />
          <div style={{ minWidth: 0 }}>
            <span data-dn-size="xs">{description}</span>
            <span
              data-dn-size="xs"
              style={{ color: "var(--dn-color-text-muted)" }}
            >
              {token}
            </span>
          </div>
        </div>
      ))}
    </div>
  );
}

Dark mode

Color mode is set with data-dn-color-mode. Put it on any ancestor to switch the Density color variables inherited by that subtree.

light
dark
function ColorModePanel({ mode }: { mode: "light" | "dark" }) {
  return (
    <div
      data-dn-color-mode={mode}
      data-dn-depth="2"
      style={{ minWidth: 0, padding: "1rem" }}
    >
      <div style={{ color: "var(--dn-color-text)" }}>
        <span data-dn-size="xs" style={{ fontWeight: 500 }}>
          {mode}
        </span>
        <div style={{ display: "flex", gap: "0.5rem", marginTop: "0.5rem" }}>
          <button data-dn-clickable data-dn-emphasis="primary" type="button">
            Primary
          </button>
        </div>
      </div>
    </div>
  );
}

export default function ColorModeExample() {
  return (
    <div
      style={{
        display: "grid",
        gap: "0.75rem",
        gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
      }}
    >
      <ColorModePanel mode="light" />
      <ColorModePanel mode="dark" />
    </div>
  );
}

Border

Set data-dn-border on a surface to use Density’s layout-safe inset border. It accepts none or border; omit it when the element has no border.

Use data-dn-border="border" for standard surface outlines. Do not write custom surface borders when this attribute expresses the intended treatment.

none
border
import { View } from "density-base-ui";

export default function BorderExample() {
  return (
    <View align="start" gap="sm" padding={false}>
      <div data-dn-border="none" data-dn-depth="1" style={{ padding: "12px" }}>
        none
      </div>
      <div
        data-dn-border="border"
        data-dn-depth="1"
        style={{ padding: "12px" }}
      >
        border
      </div>
    </View>
  );
}

Depth

Depth creates light surfaces. data-dn-depth="1" is pure white, and each higher depth is slightly grayer.

Depth sets background and the default text color. Combine it with data-dn-border, data-dn-radius, and data-dn-shadow to build a complete surface treatment.

Depth 1
Depth 2
Depth 3
Depth 4
Depth 5
Depth 6
Depth 7
import { View } from "density-base-ui";

export default function DepthsExample() {
  return (
    <View align="stretch" gap="sm" padding={true}>
      <View depth="1">Depth 1</View>
      <View depth="2">Depth 2</View>
      <View depth="3">Depth 3</View>
      <View depth="4">Depth 4</View>
      <View depth="5">Depth 5</View>
      <View depth="6">Depth 6</View>
      <View depth="7">Depth 7</View>
    </View>
  );
}

Clickable

Use data-dn-clickable to apply a Density-native button-like style. Clickable elements set color, background, and cursor styles and respond to hover, keyboard focus, press, selected, and disabled states.

The clickable attribute should be used on a semantic button or link element.

Use native disabled on form controls, data-disabled on custom controls, and data-selected when the control represents a selected item.

Use data-dn-emphasis and data-dn-color to choose the control’s visual priority and semantic status. Do not add data-dn-typable: elements with both attributes use the typable field treatment.

import { useState } from "react";

import { View } from "density-base-ui";

export default function ClickableExample() {
  const [isSelected, setIsSelected] = useState(false);

  return (
    <View align="start" padding={false}>
      <button
        type="button"
        aria-pressed={isSelected}
        data-dn-clickable
        data-selected={isSelected ? "" : undefined}
        data-dn-emphasis="secondary"
        onClick={() => setIsSelected((currentValue) => !currentValue)}
      >
        {isSelected ? "Selected" : "Select item"}
      </button>
    </View>
  );
}

Emphasis

Emphasis is set with data-dn-emphasis. Clickable controls support primary, secondary, outline, ghost-primary, ghost-secondary, and ghost-tertiary; typable controls use their input-specific emphasis tokens. Put it on an HTML ancestor to create a scope for clickable and typable descendants in its light DOM. An explicit descendant attribute overrides the scope.

const emphasisValues = [
  "primary",
  "secondary",
  "outline",
  "ghost-secondary",
  "ghost-tertiary",
];

export default function AdvancedEmphasisExample() {
  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: "0.75rem" }}>
      {emphasisValues.map((emphasis) => (
        <button
          data-dn-clickable
          data-dn-emphasis={emphasis}
          key={emphasis}
          type="button"
        >
          {emphasis}
        </button>
      ))}
    </div>
  );
}

Emphasis is inherited by clickable and typable descendants in the light DOM. The example below shows an emphasis scope with an explicit descendant override.

Emphasis scopes do not cross React portals or Shadow DOM. Give portal content its own explicit emphasis, or use an overlay component that forwards the context into its portal.

export default function InheritedEmphasisExample() {
  return (
    <div data-dn-emphasis="primary" style={{ display: "grid", gap: "0.75rem" }}>
      <span>Primary scope</span>
      <input data-dn-typable placeholder="Inherited primary input" />
      <button data-dn-clickable type="button">
        Inherited primary button
      </button>
      <div
        data-dn-emphasis="ghost-secondary"
        style={{ display: "grid", gap: "0.75rem" }}
      >
        <span>Nested ghost secondary scope</span>
        <input data-dn-typable placeholder="Inherited ghost secondary input" />
        <button data-dn-clickable type="button">
          Inherited ghost secondary button
        </button>
      </div>
      <button data-dn-clickable data-dn-emphasis="ghost-tertiary" type="button">
        Explicit ghost tertiary override
      </button>
    </div>
  );
}
Primary scope
Nested ghost secondary scope

Radius

Set data-dn-radius to give a surface consistent corners. It accepts none, 2xs, xs, sm, md, lg, xl, 2xl, 3xl, or full. Use full for pill and circular controls.

none
2xs
xs
sm
md
lg
xl
2xl
3xl
full
import { View } from "density-base-ui";

export default function RadiusExample() {
  return (
    <View align="start" gap="sm" orientation="horizontal" padding={false} wrap>
      <div data-dn-radius="none" data-dn-depth="4" style={{ padding: "12px" }}>
        none
      </div>
      <div data-dn-radius="2xs" data-dn-depth="4" style={{ padding: "12px" }}>
        2xs
      </div>
      <div data-dn-radius="xs" data-dn-depth="4" style={{ padding: "12px" }}>
        xs
      </div>
      <div data-dn-radius="sm" data-dn-depth="4" style={{ padding: "12px" }}>
        sm
      </div>
      <div data-dn-radius="md" data-dn-depth="4" style={{ padding: "12px" }}>
        md
      </div>
      <div data-dn-radius="lg" data-dn-depth="4" style={{ padding: "12px" }}>
        lg
      </div>
      <div data-dn-radius="xl" data-dn-depth="4" style={{ padding: "12px" }}>
        xl
      </div>
      <div data-dn-radius="2xl" data-dn-depth="4" style={{ padding: "12px" }}>
        2xl
      </div>
      <div data-dn-radius="3xl" data-dn-depth="4" style={{ padding: "12px" }}>
        3xl
      </div>
      <div data-dn-radius="full" data-dn-depth="4" style={{ padding: "12px" }}>
        full
      </div>
    </View>
  );
}

Shadow

Set data-dn-shadow to apply a Density elevation shadow. It accepts none, 2xs, xs, sm, md, lg, xl, 2xl, or 3xl.

none
2xs
xs
sm
md
lg
xl
2xl
3xl
import { Panel, View } from "density-base-ui";

export default function ShadowExample() {
  return (
    <Panel depth="7">
      <View
        align="start"
        gap="xl"
        orientation="horizontal"
        padding={false}
        wrap
      >
        <div
          data-dn-depth="1"
          data-dn-radius="sm"
          data-dn-shadow="none"
          style={{ padding: "12px" }}
        >
          none
        </div>
        <div
          data-dn-depth="1"
          data-dn-radius="sm"
          data-dn-shadow="2xs"
          style={{ padding: "12px" }}
        >
          2xs
        </div>
        <div
          data-dn-depth="1"
          data-dn-radius="sm"
          data-dn-shadow="xs"
          style={{ padding: "12px" }}
        >
          xs
        </div>
        <div
          data-dn-depth="1"
          data-dn-radius="sm"
          data-dn-shadow="sm"
          style={{ padding: "12px" }}
        >
          sm
        </div>
        <div
          data-dn-depth="1"
          data-dn-radius="sm"
          data-dn-shadow="md"
          style={{ padding: "12px" }}
        >
          md
        </div>
        <div
          data-dn-depth="1"
          data-dn-radius="sm"
          data-dn-shadow="lg"
          style={{ padding: "12px" }}
        >
          lg
        </div>
        <div
          data-dn-depth="1"
          data-dn-radius="sm"
          data-dn-shadow="xl"
          style={{ padding: "12px" }}
        >
          xl
        </div>
        <div
          data-dn-depth="1"
          data-dn-radius="sm"
          data-dn-shadow="2xl"
          style={{ padding: "12px" }}
        >
          2xl
        </div>
        <div
          data-dn-depth="1"
          data-dn-radius="sm"
          data-dn-shadow="3xl"
          style={{ padding: "12px" }}
        >
          3xl
        </div>
      </View>
    </Panel>
  );
}

Size

Size is set with data-dn-size: 2xs, xs, sm, md, lg, xl, 2xl, or 3xl. Put it on an HTML element to set its Density size, or on an ancestor to create a size scope in its light DOM. An explicit descendant data-dn-size overrides the surrounding scope.

Size scopes do not cross React portals or Shadow DOM. Give an overlay its own explicit size, or use an overlay component that forwards the context into its portal.

const sizes = ["xs", "sm", "md", "lg"];

export default function AdvancedSizeExample() {
  return (
    <div
      style={{
        alignItems: "center",
        display: "flex",
        flexWrap: "wrap",
        gap: "0.75rem",
      }}
    >
      {sizes.map((size) => (
        <button data-dn-clickable data-dn-size={size} key={size} type="button">
          {size}
        </button>
      ))}
    </div>
  );
}

Size is inherited by Density components in the light DOM. The example below shows a size scope with an explicit descendant override.

export default function InheritedSizeExample() {
  return (
    <div data-dn-size="lg" style={{ display: "grid", gap: "0.75rem" }}>
      <label>
        <span>Inherited label</span>
        <input
          data-dn-emphasis="primary"
          data-dn-typable
          defaultValue=""
          placeholder="Inherited input"
        />
      </label>
      <button data-dn-clickable type="button">
        Inherited button
      </button>
      <button data-dn-clickable data-dn-size="xs" type="button">
        Explicit xs override
      </button>
    </div>
  );
}

Reveal

Add data-dn-reveal to a container and data-dn-reveal-target to its children to switch content when the container is hovered or contains focus. Use data-dn-reveal-target="hover" for actions that appear while active and data-dn-reveal-target="not-hover" for the resting content.

Reveal behavior applies on devices that support hover. Keyboard focus also reveals the active target, so controls remain available to keyboard users; keep essential actions available by another path on touch devices.

Hover to reveal actions
import { View } from "density-base-ui";

export default function RevealExample() {
  return (
    <View align="start" padding={false}>
      <div data-dn-reveal>
        <span data-dn-reveal-target="not-hover">Hover to reveal actions</span>
        <button type="button" data-dn-clickable data-dn-reveal-target="hover">
          Edit
        </button>
      </div>
    </View>
  );
}

Typable

Add data-dn-typable to a semantic text-entry control such as input or textarea to apply Density’s field background, foreground, focus, and disabled states. The element keeps its native editing and accessibility behavior.

Typable elements respond to keyboard focus and disabled state. Use native disabled on form controls, or data-disabled when building an accessible custom field.

Use data-dn-emphasis to select the field’s visual priority. Typable styling owns field state, while the surrounding component owns its geometry and layout.

import { useState } from "react";

import { View } from "density-base-ui";

export default function TypableExample() {
  const [value, setValue] = useState("");

  return (
    <View align="start" padding={false}>
      <label>
        Project name
        <input
          data-dn-typable
          value={value}
          onChange={(event) => setValue(event.target.value)}
          placeholder="Untitled project"
        />
      </label>
    </View>
  );
}

Typography

Density’s default font family is inherited by the page. Set data-dn-font-family="default" to apply it explicitly, or use data-dn-font-family="mono" for code, identifiers, and numeric values that benefit from fixed-width alignment.

Use the default family for ordinary interface text. Reserve the monospace family for data where character alignment makes scanning easier.

Primary 123,456.78Secondary 123,456.78Ghost secondary 123,456.78Ghost tertiary 123,456.78Monospace 123,456.78
import { View } from "density-base-ui";

export default function NumberTypographyExample() {
  return (
    <View align="start" gap="md" padding={false}>
      <span data-dn-emphasis="primary">Primary 123,456.78</span>
      <span data-dn-emphasis="secondary">Secondary 123,456.78</span>
      <span data-dn-emphasis="ghost-secondary">Ghost secondary 123,456.78</span>
      <span data-dn-emphasis="ghost-tertiary">Ghost tertiary 123,456.78</span>
      <span data-dn-emphasis="primary" data-dn-font-family="mono">
        Monospace 123,456.78
      </span>
    </View>
  );
}