Radius & Shadow Tuner
Drag handles to adjust a box's corner radius, shadow and padding with live readouts and the resulting CSS — ideal for demonstrating design tokens in docs.
An interactive specimen for docs sites and design-system pages. The centre box defaults to 240×160: drag the round top-right handle inward along the diagonal to change the corner radius, the handle below the box downward to deepen the shadow, and the right-hand handle sideways to change the padding. Each handle carries a readout label that follows it, with digits that roll into place. The code panel underneath mirrors border-radius, box-shadow and padding and copies them in one click. Values snap to multiples of step and are clamped to max; both controlled and uncontrolled modes are supported, and initial values can come straight from your design tokens.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add radius-shadow-tuner.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<RadiusShadowTuner/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/radius-shadow-tuner.en.jsonOr, once registries are configured in components.json, install it as @webberui/radius-shadow-tuner.
Usage
import { RadiusShadowTuner } from "@/components/ui/radius-shadow-tuner";
<RadiusShadowTuner
defaultValues={{ radius: 24, shadow: 32, padding: 16 }}
step={4}
onChange={(v) => console.log(v.radius, v.shadow, v.padding)}
>
<p className="text-sm font-semibold">Order shipped</p>
</RadiusShadowTuner>
// Radius only, capped at 40px
<RadiusShadowTuner properties={["radius"]} max={{ radius: 40 }} showCode={false} />Override the CSS variables on an ancestor to restyle it:
.my-docs {
--wb-radius-drag-accent: #3b82f6; /* handle and readout colour */
--wb-radius-drag-roll: 160ms; /* per-digit roll duration */
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
values | RadiusShadowValues | — | Controlled values; when omitted the component manages them internally (uncontrolled) |
defaultValues | Partial<RadiusShadowValues> | { radius: 16, shadow: 24, padding: 16 } | Initial values in uncontrolled mode — pass your design tokens straight in; missing fields use the built-in defaults |
onChange | (values: RadiusShadowValues) => void | — | Called whenever any value changes, with the full set of three values |
properties | RadiusShadowProperty[] | ["radius", "shadow", "padding"] | Which handles to show (also decides which CSS the code panel lists) |
max | Partial<RadiusShadowValues> | { radius: 64, shadow: 48, padding: 48 } | Per-property maximum (px); missing fields use the defaults |
step | number | 1 | Step for dragging and arrow keys; values snap to multiples of it (typically 1 or 4) |
showCode | boolean | true | Show the CSS code panel and copy button underneath |
stiffness | number | 500 | Stiffness of the spring the handle bounces back with on release |
damping | number | 30 | Damping of the spring the handle bounces back with on release |
children | React.ReactNode | — | Content rendered inside the box; a hint is shown when omitted |
className | string | — | Forwarded to the outermost container |
RadiusShadowValues
| Field | Type | Default | Description |
|---|---|---|---|
radius | number | 16 | Corner radius (px) |
shadow | number | 24 | Shadow blur radius (px); the vertical offset is derived as 3/8 of the blur |
padding | number | 16 | Padding (px) |
RadiusShadowProperty is "radius" \| "shadow" \| "padding".
How it works
- The radius handle sits at
(width − r, r)inside the box; while dragging, only the pointer movement projected onto the inward diagonal counts, so dragging at a slight angle never jitters. - Dragging uses pointer events plus
setPointerCapture, so the drag continues even when the pointer leaves the handle; there is no drag inertia — it stops the moment you let go. - The box's
border-radius,box-shadowandpaddingare driven directly by motion values, so dragging does not wait for the whole component tree to re-render; React state only updates when the snapped value actually changes. - The shadow's y offset is 3/8 of the blur (a 32px blur gives a 12px offset), in a fixed
rgba(0, 0, 0, 0.18). - When the right-hand padding handle is enabled, a dashed guide inside the box marks the edge of the content area.
- Each readout digit is a 0–9 column scrolled to the target with
translateY; the duration comes from--wb-radius-drag-roll. - The copy button calls
navigator.clipboard.writeTextinside the click handler and shows "Copied" for 1.5 seconds on success.
Accessibility
- Every handle is a
role="slider"with anaria-label(Radius / Shadow / Padding),aria-valuemin,aria-valuemax,aria-valuenowand anaria-valuetextthat includes the unit - Once a handle is focused, the arrow keys move it ±step, Shift+arrow or PageUp / PageDown move it ±10, and Home / End jump to 0 and the maximum; keyboard focus shows a focus-visible ring
- The rolling readout digits are hidden from assistive technology to avoid double announcements; the copy result is announced through a
role="status"live region - When the user has reduce motion enabled at the system level, readout digits swap without rolling, and handles neither grow on press nor bounce back on release
Node Wire Canvas
A node-based workflow canvas: freely draggable nodes, bezier wires between ports, drag-to-connect from any port, flowing pulses along edges while running, plus pan and zoom.
Master Switch Cascade
A master switch cascade React component: flip one master switch and the child switches in a grid follow in a distance-based ripple, each cell flashing a ring as it flips; children stay individually toggleable and the master reflects on, off or mixed.