A physics-based lighting system for CSS. Define a light source, and every shadow, highlight and surface gradient follows from it — calibrated against Blender raytraces.
472
stars
88
commits
TypeScript
primary language
Sep 9, 2026
updated
A physics-based lighting system for CSS. Define a light source, and every shadow, highlight, and surface gradient follows from it — no more hand-tuned shadow-sm / shadow-md / shadow-lg.
Live Demo • Documentation • npm (@ambientcss/css) • npm (@ambientcss/components)

The same device, twice: raytraced in Blender (ambient3d) on one side of the wipe, live DOM under @ambientcss/css on the other — same geometry, same light vector, box-shadow instead of Cycles. The faceplate is a plain .ambient .amb-surface .amb-chamfer-2 box, and every control sits on the spacing scale — tight 12 / normal 20 / loose 32 mm, including the panel's own edge margin. Built by tools/hero-gif; the full clip is ambientcss.mp4.
Traditional CSS shadow scales are decorative — a card with shadow-lg next to a button with shadow-sm doesn't imply a shared physical scene. They're just two unrelated blur values coexisting on the same page.
Ambient CSS starts from physical lighting principles. You describe a lighting environment — light direction, key/fill intensity, hue, and elevation — and all shadows, edge highlights, and surface gradients follow deterministically. Change the light vector on a container, and every child element updates consistently.
The engine models UI surfaces as physical materials under a two-light system (key light + fill light). Every element reads shared CSS custom properties and generates a 5-layer composite box-shadow:
Each element combines five core material concerns:
Structure ─── ambient (enables lighting calculations)
Surface ───── flat / concave / convex (background lighting gradient)
Edge ──────── chamfer / fillet / groove (beveled & rounded edge cuts)
Material ──── matte / shiny / glass (specular reflection & translucency)
Depth ──────── elevation 0–3 (drop shadow height)
Lighting behavior is physically grounded and calibrated against raytraced 3D reference models built with Blender (ambient3d).
npm install @ambientcss/css
<link rel="stylesheet" href="node_modules/@ambientcss/css/dist/ambient.css" />
<!-- Set up light environment on any parent -->
<div class="amb-light-tl">
<button class="ambient amb-surface amb-chamfer amb-elevation-1 amb-rounded">
Click me
</button>
</div>
npm install @ambientcss/components @ambientcss/css
import { AmbientProvider, AmbientButton, AmbientKnob, AmbientPanel } from "@ambientcss/components";
import "@ambientcss/css/ambient.css";
import "@ambientcss/components/styles.css";
function App() {
return (
<AmbientProvider theme={{ lightX: -1, lightY: -1, keyLight: 0.9, fillLight: 0.7 }}>
<AmbientPanel>
<AmbientButton>Press</AmbientButton>
<AmbientKnob label="Gain" defaultValue={50} />
</AmbientPanel>
</AmbientProvider>
);
}
Each component is a preset: a mechanism that owns the kinematics, the value and the ARIA, paired with a set of parts that own the paint. Reach past the preset and the mechanism will wear whatever you draw:
<AmbientRotary
value={gain} onChange={setGain}
travel={240} input="drag"
parts={{ base: <KnobBody flush />, actuator: <MyPointer /> }}
/>
The control publishes --ambx-percent, --ambx-angle and --ambx-size on its
own root, so a part can be pure CSS. See
Composing controls.
| Package | Path | Description |
|---|---|---|
@ambientcss/css | packages/ambient-css | Pure CSS lighting framework — zero dependencies, works with any framework or plain HTML |
@ambientcss/components | packages/ambient-components | Tactile React component library — composable control mechanisms plus grounded hardware presets |
ambient3d | ambient3d | Parametric Blender 3D component kit & ground-truth raytracing calibration engine |
docs | apps/docs | Interactive Docusaurus documentation website & live code playground |
demo | apps/demo | Hardware synthesizer & audio gear live demo application |
Set on any ancestor element, inherited by all descendants:
.amb-light-tl .amb-light-tr .amb-light-bl .amb-light-br .amb-light-top .amb-light-bottom .amb-light-left .amb-light-right
Custom CSS variables for granular light manipulation:
.my-scene {
--amb-light-x: -0.7; /* Horizontal (-1 left, 1 right) */
--amb-light-y: -0.8; /* Vertical (-1 top, 1 bottom) */
--amb-key-light-intensity: 0.9; /* Primary directional light (0..1) */
--amb-fill-light-intensity: 0.6; /* Ambient fill light (0..1) */
--amb-light-hue: 234; /* Light hue (0..360) */
--amb-light-saturation: 15%; /* Light saturation */
}
.amb-surface .amb-surface-concave .amb-surface-concave-h .amb-surface-convex
A surface is its material under the scene's light, not a fixed colour, so one class covers every hue and shade:
.my-panel {
--amb-albedo: crimson; /* the colour under full illumination */
--amb-shade: 1; /* multiplier on that reflectance */
}
--amb-albedo takes any CSS colour and both variables inherit, so a panel
colours everything inside it — grooves, dishes and the components package's
knobs and keys all read the same lit tone. Dim the key light and a crimson
panel darkens like crimson; give the lamp a hue and it takes the cast.
The former .amb-surface-darker, -darkest, -lighter and -lightest
classes were five fixed albedos of this one law; the equivalent shades are
0.38, 0.07, 1.11 and 1.16.
.amb-chamfer .amb-chamfer-2 .amb-fillet .amb-fillet-2 .amb-groove
.amb-mat-matte .amb-mat-shiny .amb-mat-glass .amb-mat-brushed
.amb-mat-brushed-round .amb-mat-blasted
.amb-mat-brushed, .amb-mat-brushed-round and .amb-mat-blasted carry a
fitted micro-relief rather than a gloss: the tile holds a raw height field and
its inverse, and only the offset between them tracks the light, so the bumps
stay put and the shading crosses over them. Scale it with --amb-grain-amount
(default 1).
.amb-mat-brushed-round is the same aluminium spun about the element's centre
instead of run across it — the lathe finish on a knob cap — so put it on round
faces. Its grain is a conic gradient rather than a repeating tile, and the two
bright arcs that swing with the lamp fall out of the same offset the linear
grain uses, because a fixed offset is tangent to a circular groove in only two
places.
Both brushed finishes also carry a broad specular sheen, painted on the host's
own background like .amb-mat-shiny's. It is anisotropic in the grain's own
direction: a band across the grain on the linear metal (so --amb-light-y
moves it and --amb-light-x does not), and a pair of opposed lobes on the
lamp's axis, plus a converged hotspot, on the spun one.
.amb-elevation-0 .amb-elevation-1 .amb-elevation-2 .amb-elevation-3.amb-thickness-0 .amb-thickness-1 .amb-thickness-2.amb-emit-red .amb-emit-green .amb-emit-amber .amb-emit-cyan .amb-emit-blue .amb-emit-white.amb-glow.amb-rounded .amb-rounded-md .amb-rounded-lg .amb-rounded-xl .amb-rounded-full
Check out the examples/ directory for ready-to-run starters:
css-basic, css-purecss, css-tailwindreact-ambientcss, react-basic, react-bootstrap, react-tailwind# Clone repository
git clone https://github.com/kikkupico/ambientcss.git
cd ambientcss
# Install workspace dependencies
pnpm install
# Build all packages
pnpm build
# Typecheck workspace
pnpm typecheck
# Start demo app
pnpm --filter demo dev
# Start documentation site
pnpm docs:dev
# Run release checks
pnpm release:check
See RELEASING.md for publishing workflows and changeset instructions.
76 commits
12 commits
Hacker News (2)
TypeScript
41.8%
Python
28.1%
CSS
21.5%
JavaScript
5.3%
MDX
2.8%
A physics-based lighting system for CSS. Define a light source, and every shadow, highlight and surface gradient follows from it — calibrated against Blender raytraces.
472
stars
88
commits
TypeScript
primary language
Sep 9, 2026
updated
A physics-based lighting system for CSS. Define a light source, and every shadow, highlight, and surface gradient follows from it — no more hand-tuned shadow-sm / shadow-md / shadow-lg.
Live Demo • Documentation • npm (@ambientcss/css) • npm (@ambientcss/components)

The same device, twice: raytraced in Blender (ambient3d) on one side of the wipe, live DOM under @ambientcss/css on the other — same geometry, same light vector, box-shadow instead of Cycles. The faceplate is a plain .ambient .amb-surface .amb-chamfer-2 box, and every control sits on the spacing scale — tight 12 / normal 20 / loose 32 mm, including the panel's own edge margin. Built by tools/hero-gif; the full clip is ambientcss.mp4.
Traditional CSS shadow scales are decorative — a card with shadow-lg next to a button with shadow-sm doesn't imply a shared physical scene. They're just two unrelated blur values coexisting on the same page.
Ambient CSS starts from physical lighting principles. You describe a lighting environment — light direction, key/fill intensity, hue, and elevation — and all shadows, edge highlights, and surface gradients follow deterministically. Change the light vector on a container, and every child element updates consistently.
The engine models UI surfaces as physical materials under a two-light system (key light + fill light). Every element reads shared CSS custom properties and generates a 5-layer composite box-shadow:
Each element combines five core material concerns:
Structure ─── ambient (enables lighting calculations)
Surface ───── flat / concave / convex (background lighting gradient)
Edge ──────── chamfer / fillet / groove (beveled & rounded edge cuts)
Material ──── matte / shiny / glass (specular reflection & translucency)
Depth ──────── elevation 0–3 (drop shadow height)
Lighting behavior is physically grounded and calibrated against raytraced 3D reference models built with Blender (ambient3d).
npm install @ambientcss/css
<link rel="stylesheet" href="node_modules/@ambientcss/css/dist/ambient.css" />
<!-- Set up light environment on any parent -->
<div class="amb-light-tl">
<button class="ambient amb-surface amb-chamfer amb-elevation-1 amb-rounded">
Click me
</button>
</div>
npm install @ambientcss/components @ambientcss/css
import { AmbientProvider, AmbientButton, AmbientKnob, AmbientPanel } from "@ambientcss/components";
import "@ambientcss/css/ambient.css";
import "@ambientcss/components/styles.css";
function App() {
return (
<AmbientProvider theme={{ lightX: -1, lightY: -1, keyLight: 0.9, fillLight: 0.7 }}>
<AmbientPanel>
<AmbientButton>Press</AmbientButton>
<AmbientKnob label="Gain" defaultValue={50} />
</AmbientPanel>
</AmbientProvider>
);
}
Each component is a preset: a mechanism that owns the kinematics, the value and the ARIA, paired with a set of parts that own the paint. Reach past the preset and the mechanism will wear whatever you draw:
<AmbientRotary
value={gain} onChange={setGain}
travel={240} input="drag"
parts={{ base: <KnobBody flush />, actuator: <MyPointer /> }}
/>
The control publishes --ambx-percent, --ambx-angle and --ambx-size on its
own root, so a part can be pure CSS. See
Composing controls.
| Package | Path | Description |
|---|---|---|
@ambientcss/css | packages/ambient-css | Pure CSS lighting framework — zero dependencies, works with any framework or plain HTML |
@ambientcss/components | packages/ambient-components | Tactile React component library — composable control mechanisms plus grounded hardware presets |
ambient3d | ambient3d | Parametric Blender 3D component kit & ground-truth raytracing calibration engine |
docs | apps/docs | Interactive Docusaurus documentation website & live code playground |
demo | apps/demo | Hardware synthesizer & audio gear live demo application |
Set on any ancestor element, inherited by all descendants:
.amb-light-tl .amb-light-tr .amb-light-bl .amb-light-br .amb-light-top .amb-light-bottom .amb-light-left .amb-light-right
Custom CSS variables for granular light manipulation:
.my-scene {
--amb-light-x: -0.7; /* Horizontal (-1 left, 1 right) */
--amb-light-y: -0.8; /* Vertical (-1 top, 1 bottom) */
--amb-key-light-intensity: 0.9; /* Primary directional light (0..1) */
--amb-fill-light-intensity: 0.6; /* Ambient fill light (0..1) */
--amb-light-hue: 234; /* Light hue (0..360) */
--amb-light-saturation: 15%; /* Light saturation */
}
.amb-surface .amb-surface-concave .amb-surface-concave-h .amb-surface-convex
A surface is its material under the scene's light, not a fixed colour, so one class covers every hue and shade:
.my-panel {
--amb-albedo: crimson; /* the colour under full illumination */
--amb-shade: 1; /* multiplier on that reflectance */
}
--amb-albedo takes any CSS colour and both variables inherit, so a panel
colours everything inside it — grooves, dishes and the components package's
knobs and keys all read the same lit tone. Dim the key light and a crimson
panel darkens like crimson; give the lamp a hue and it takes the cast.
The former .amb-surface-darker, -darkest, -lighter and -lightest
classes were five fixed albedos of this one law; the equivalent shades are
0.38, 0.07, 1.11 and 1.16.
.amb-chamfer .amb-chamfer-2 .amb-fillet .amb-fillet-2 .amb-groove
.amb-mat-matte .amb-mat-shiny .amb-mat-glass .amb-mat-brushed
.amb-mat-brushed-round .amb-mat-blasted
.amb-mat-brushed, .amb-mat-brushed-round and .amb-mat-blasted carry a
fitted micro-relief rather than a gloss: the tile holds a raw height field and
its inverse, and only the offset between them tracks the light, so the bumps
stay put and the shading crosses over them. Scale it with --amb-grain-amount
(default 1).
.amb-mat-brushed-round is the same aluminium spun about the element's centre
instead of run across it — the lathe finish on a knob cap — so put it on round
faces. Its grain is a conic gradient rather than a repeating tile, and the two
bright arcs that swing with the lamp fall out of the same offset the linear
grain uses, because a fixed offset is tangent to a circular groove in only two
places.
Both brushed finishes also carry a broad specular sheen, painted on the host's
own background like .amb-mat-shiny's. It is anisotropic in the grain's own
direction: a band across the grain on the linear metal (so --amb-light-y
moves it and --amb-light-x does not), and a pair of opposed lobes on the
lamp's axis, plus a converged hotspot, on the spun one.
.amb-elevation-0 .amb-elevation-1 .amb-elevation-2 .amb-elevation-3.amb-thickness-0 .amb-thickness-1 .amb-thickness-2.amb-emit-red .amb-emit-green .amb-emit-amber .amb-emit-cyan .amb-emit-blue .amb-emit-white.amb-glow.amb-rounded .amb-rounded-md .amb-rounded-lg .amb-rounded-xl .amb-rounded-full
Check out the examples/ directory for ready-to-run starters:
css-basic, css-purecss, css-tailwindreact-ambientcss, react-basic, react-bootstrap, react-tailwind# Clone repository
git clone https://github.com/kikkupico/ambientcss.git
cd ambientcss
# Install workspace dependencies
pnpm install
# Build all packages
pnpm build
# Typecheck workspace
pnpm typecheck
# Start demo app
pnpm --filter demo dev
# Start documentation site
pnpm docs:dev
# Run release checks
pnpm release:check
See RELEASING.md for publishing workflows and changeset instructions.
Hacker News (2)
76 commits
12 commits
TypeScript
41.8%
Python
28.1%
CSS
21.5%
JavaScript
5.3%
MDX
2.8%