Circular numeric control with a tick ring that fills from min to the current value, drag axis, wrap, invert, progress outline, sizes, and label placement. Prefer Slider for a linear track, and Tick Slider for bound labels with a tick gauge.
Also known as: knob, circular slider, radial slider, rotary, circular control, radial control
Install with the CLI, or copy the component source into your project.
Add dial (npm)
1npx --yes -p @default-file/ui df-ui add dial
Single value on a 0 to 100 ring. Drag in any direction. scrubSound follows position.
sm, md, and lg remap ring diameter, ticks, thumb, and type.
A short label and the live value sit in the center.
Place the label at the top, right, bottom, or left of the ring. labelGap sets the space between the label and the ring.
valueFormat percent maps min to max onto 0% to 100%. The inside label stays muted so the percent is primary.
thumb tick accents the nearest mark and hides the dot.
faceBackground, faceBorder, and accent restyle the ring.
dragAxis all uses every direction. horizontal uses only left and right. vertical uses only up and down. The indicator still travels around the ring.
wrap with min 0 and max 360 spins with no stop. Ticks do not fill from an origin. invert reverses drag so the value can go negative when min is below 0.
progress draws a ring on the face edge from startAngle to the current value. Ticks sit inside that rim. wrap with min 0 and max 360 keeps the ring spinning.
Blocks pointer and keyboard interaction.
Circular numeric control with a tick ring that fills from min to the current value, optional center or edge label, and a moving indicator. Pointer travel raises or lowers the value. wrap spins through a full turn with no origin fill. invert reverses drag so reverse rotation can go negative. progress draws a rotation outline on the face. Prefer Slider for a linear track, and Tick Slider for bound labels with a tick gauge.
| Prop | Type | Default | Notes |
|---|---|---|---|
| min | number | 0 | Minimum value. |
| max | number | 100 | Maximum value. |
| step | number | 1 | Increment between committed values. Values of 0 or less fall back to 1. |
| value | number | - | Controlled value. Snapped to step and clamped, or wrapped when wrap is set. |
| defaultValue | number | min | Uncontrolled initial value. |
| onValueChange | (value: number) => void | - | Called when a committed value changes. |
| wrap | boolean | false | When true, dragging or stepping past max continues at min, and past min continues at max. Ticks do not fill from an origin unless tickFill is set. Leave false to clamp. |
| invert | boolean | false | Reverse pointer travel and arrow keys. Right and up decrease. Set min below 0 so reverse rotation can go negative. |
| dragAxis | "all" | "horizontal" | "vertical" | "all" | Pointer axis that changes the value. all maps right and up to increase, left and down to decrease, and diagonals combine. horizontal uses only left and right. vertical uses only up and down. The indicator still travels around the ring. |
| startAngle | number | 0 | Ring heading in degrees clockwise from the top. Gauge min sits here when min is 0 or greater. When min is below 0 and max is above 0, value 0 sits here. |
| sweep | number | 360 | How much of the ring the range uses. 360 is a full ring. Use less than 360 to keep the ends on distinct points. |
| formatValue | (value: number) => string | - | Formats the visible value and aria valuetext. Overrides valueFormat. |
| valueFormat | "number" | "percent" | "number" | Built in readout format. number uses step precision. percent shows position across min to max. Ignored when formatValue is set. |
| showValue | boolean | false | Show the formatted value in the center of the ring. |
| disabled | boolean | - | Disables pointer and keyboard interaction. |
| Prop | Type | Default | Notes |
|---|---|---|---|
| label | React.ReactNode | - | Optional label. Use a short mark for inside, or any copy for an outside edge. An inside label next to a value uses smaller muted type so the value stays primary. |
| labelPosition | "inside" | "top" | "bottom" | "left" | "right" | "inside" | Where the label sits. inside places it in the center of the ring. top, bottom, left, and right sit outside the ring. |
| labelGap | string | - | Gap between an outside label and the ring. Prefer a kit length token such as calc(4 * var(--spacing-unit)). Ignored when the label is inside. |
| Prop | Type | Default | Notes |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "md" | Ring diameter and matching tick, thumb, and type sizes. sm is compact, md is the default, lg is a large ring. |
| thumb | "dot" | "tick" | "dot" | Value indicator. dot is a circular thumb on the ring. tick hides the thumb and accents the nearest tick. |
| tickCount | number | 36 | Number of ticks around the ring. Minimum 2. |
| showTicks | boolean | true | Show the tick ring. |
| tickFill | boolean | true when wrap is off and min is 0 or greater | Fill ticks from min to the current value. Defaults to on for a gauge. Off when wrap is set or min is below 0 so the ring has no origin. Set true to force fill. |
| tickRadius | "none" | "xxs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "full" | "none" | End of each default tick. none is square. full is round. |
| thumbRadius | "none" | "xxs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "full" | "full" | Corner radius of the dot thumb. |
| renderTick | (tick: { index: number; count: number; active: boolean; filled: boolean; angle: number; value: number }) => React.ReactNode | - | Replace each default tick with custom content. filled is true from min to the current value. active is true on the nearest tick when thumb is tick. |
| accent | string | - | Fill for the dot thumb and the nearest tick when thumb is tick. Prefer a kit color token. |
| tickMuted | string | - | Fill for ticks past the current value. Prefer a kit color token. |
| tickFilled | string | - | Fill for ticks from min to the current value. Prefer a kit color token. |
| tickActive | string | - | Fill for the nearest tick when thumb is tick. Prefer a kit color token. |
| labelColor | string | - | Label text color. Prefer a kit color token. |
| valueColor | string | - | Value text color. Prefer a kit color token. |
| faceBackground | string | - | Fill behind the ring. Transparent by default. |
| faceBorder | string | - | Border color of the ring. Transparent by default. |
| progress | boolean | false | Draw a ring on the face edge from startAngle to the current value. The unused part of the rim stays empty. Ticks inset from that ring so they do not sit on the edge. |
| progressColor | string | - | Color of the progress ring. Prefer a kit color token. |
| tickWidth | string | - | Tick thickness. Prefer a kit length token. |
| tickLength | string | - | Tick length along the radius. Prefer a kit length token. |
| thumbSize | string | - | Dot thumb diameter. Prefer a kit length token. |
| Prop | Type | Default | Notes |
|---|---|---|---|
| tickSound | boolean | true | Play a short tick on each stepped value change while dragging or using the keyboard. Skipped when the document is hidden or reduced motion is preferred. |
| scrubSound | boolean | false | Play a smooth scrub tone while dragging. Pitch follows position and level follows drag speed. Off by default. Stops on release, unmount, or when the document is hidden. Skipped when reduced motion is preferred. |
Slider (role="slider"): The face is the slider. Arrow keys step the value. invert flips that direction. Home goes to min. End goes to max, or to the last rest when wrap is set. PageUp and PageDown move by one tenth of the range.
| Prop | Type | Default | Notes |
|---|---|---|---|
| aria-label | string | - | Accessible name when label is omitted or is not a string. |
| aria-labelledby | string | - | ID of a labeling element. Defaults to the label element when label is set and aria-label is omitted. |
| className | string | - | Additional styles on the root. |
| style | React.CSSProperties | - | Inline styles on the root. Can also override Dial CSS variables directly. |
| ref | React.Ref<HTMLDivElement> | - | Ref to the root element. |
Theme defaults live on :root. Override these globally, on a local host, or with the matching appearance props. size remaps diameter, tick, thumb, and type tokens. Prop and style overrides still win.
| Token | Type | Default | Notes |
|---|---|---|---|
| --df-dial-size | CSS length | calc(18 * var(--spacing-unit)) | Ring diameter for size md. |
| --df-dial-size-sm | CSS length | calc(12 * var(--spacing-unit)) | Ring diameter for size sm. |
| --df-dial-size-lg | CSS length | calc(28 * var(--spacing-unit)) | Ring diameter for size lg. |
| --df-dial-accent | CSS color | var(--df-coral-430) | Dot thumb fill and default active tick fill. |
| --df-dial-tick-muted | CSS color | color-mix(in oklch, var(--foreground) 14%, var(--background)) | Fill for ticks past the current value. |
| --df-dial-tick-filled | CSS color | color-mix(in oklch, var(--foreground) 32%, var(--background)) | Fill for ticks from min to the current value. |
| --df-dial-tick-active | CSS color | var(--df-dial-accent) | Nearest tick fill when thumb is tick. |
| --df-dial-tick-width | CSS length | var(--border-width-thick) | Tick thickness for size md. |
| --df-dial-tick-length | CSS length | calc(2 * var(--spacing-unit)) | Tick length for size md. |
| --df-dial-tick-radius | CSS length | 0px | End radius of a custom HTML tick. |
| --df-dial-thumb-size | CSS length | calc(var(--df-dial-tick-length) + var(--spacing-unit)) | Dot thumb diameter for size md. The center sits on the tick midpoint. One spacing unit longer so the dot covers the mark. |
| --df-dial-thumb-radius | CSS length | var(--radius-full) | Dot thumb corner radius. |
| --df-dial-face-bg | CSS color | transparent | Fill inside the ring. Transparent by default so the ticks sit on the page. |
| --df-dial-face-fill | CSS color | color-mix(in oklch, var(--muted) 88%, var(--background)) | Filled face color. Pass it as faceBackground for a disc behind the ticks. |
| --df-dial-face-border | CSS color | transparent | Ring border color. |
| --df-dial-face-border-width | CSS length | var(--border-width-thick) | Ring border width. |
| --df-dial-face-radius | CSS length | var(--radius-full) | Corner radius of the ring face. |
| --df-dial-ring-inset | CSS length | calc(1 * var(--spacing-unit)) | Space from the face edge to the outer tick tips when the face is filled or progress is set. |
| --df-dial-progress-color | CSS color | var(--df-yellow-orange-230) | Progress ring color. |
| --df-dial-progress-width | CSS length | var(--border-width-thick) | Progress ring thickness. |
| --df-dial-label-color | CSS color | var(--foreground) | Label text color. |
| --df-dial-label-font-family | CSS font family | var(--df-font-sans) | Label font. |
| --df-dial-label-font-weight | CSS font weight | var(--df-font-weight-medium) | Label weight. |
| --df-dial-label-size | CSS length | var(--df-text-xs) | Label type size for size md. |
| --df-dial-label-caption-color | CSS color | var(--muted-foreground) | Inside label color when a value is also shown. |
| --df-dial-label-caption-size | CSS length | var(--df-text-2xs) | Inside label type size when a value is also shown, for size md. |
| --df-dial-value-color | CSS color | var(--foreground) | Value text color. |
| --df-dial-value-font-family | CSS font family | var(--df-font-sans) | Value font. |
| --df-dial-value-font-weight | CSS font weight | var(--df-font-weight-semibold) | Value weight. |
| --df-dial-value-size | CSS length | var(--df-text-sm) | Value type size for size md. |
| --df-dial-label-gap | CSS length | calc(2 * var(--spacing-unit)) | Gap between an outside label and the ring. size remaps this. labelGap and style overrides still win. |
| --df-dial-center-gap | CSS length | calc(1 * var(--spacing-unit)) | Gap between an inside label and the value. |
| --df-dial-motion-duration | CSS time | var(--df-duration-slow) | Duration for thumb settle, face press, and active tick length. |
| --df-dial-motion-ease | CSS easing | var(--df-ease-emphasized) | Easing for thumb settle, face press, and active tick length. |
| --df-dial-drag-scale | CSS number | 0.98 | Face scale while dragging. |
| --df-dial-tick-active-grow | CSS length | var(--spacing-unit) | Extra tick length when thumb is tick and the mark is nearest the value. |
| --df-dial-label-max-width | CSS length | calc(16 * var(--spacing-unit)) | Maximum width of an outside label. |