Gauge and dial
A needle on a brass-bezelled scale. Gauge reads a value; Dial sets one, turned by drag or keyboard.
Runway
Days of credit left, with the auto top-up level marked.
Amber mark: auto top-up at 5 days
Auto top-up
Set like a thermostat: the level to keep above, and how much to add.
When the balance drops below $30, we add $10.
Built from
Gauge and dial is made of components that also work on their own.
Installation
Add with the CLI
Once per project, add the Bevel registry to
components.json.components.json { "registries": { "@bevel": "https://bevel-ui.vercel.app/r/{name}.json" } }Add Gauge and dial. This brings in Amount and the Bevel foundation, installs @base-ui/react and cn, and merges the styles into your global CSS.
Terminal npx shadcn@latest add @bevel/dial
Install manually
Install the dependencies.
Terminal pnpm add @base-ui/react cnAdd the Bevel foundation, if you haven't already. Light, depth, materials, motion and shape tokens, plus the utilities components use to apply them.
styles/bevel.css /* * Bevel foundation. Every component builds on these tokens and utilities; * component stylesheets only add their own palettes and shapes. * * Layers: * 1. Light one source, above and slightly in front of the screen. * 2. Depth named elevations built from the light: recessed, flush, * raised, pressed, object. * 3. Material a surface recipe (leather, plastic, metal) that reads * --bevel-material and --bevel-material-edge. * 4. Motion two curves, a spring, and durations named by purpose. * 5. Shape radii, stitching, and the type scale for amounts. */ :root { /* 1. Light */ --bevel-highlight: oklch(1 0 0 / 0.22); --bevel-highlight-strong: oklch(1 0 0 / 0.35); --bevel-shade: oklch(0 0 0 / 0.32); --bevel-shade-strong: oklch(0 0 0 / 0.5); --bevel-cast: oklch(0.25 0.03 50 / 0.22); /* Stamped into a surface: shaded on the upper inner edge, lit below. */ --bevel-deboss: 0 -1px 0 oklch(0 0 0 / 0.45), 0 1px 0 oklch(1 0 0 / 0.12); /* 3. Material defaults; components and variants override these. */ --bevel-material: oklch(0.47 0.085 52); --bevel-material-edge: oklch(0.3 0.06 44); --bevel-material-recess: oklch(0.2 0.008 260); /* neutral dark; materials set their own */ --bevel-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.7' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .7 0 0 0 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"); --bevel-grain-size: 180px; /* 2. Depth */ --bevel-depth-recessed: inset 0 1px 2px var(--bevel-shade-strong), 0 1px 0 var(--bevel-highlight); --bevel-depth-flush: inset 0 1px 0 var(--bevel-highlight), inset 0 -1px 0 var(--bevel-shade); --bevel-depth-raised: inset 0 1px 0 var(--bevel-highlight), 0 1px 0 var(--bevel-material-edge), 0 2px 0 var(--bevel-material-edge), 0 3px 5px var(--bevel-shade); --bevel-depth-pressed: inset 0 1px 2px var(--bevel-shade), 0 1px 0 var(--bevel-highlight); /* A whole object resting on the page. */ --bevel-depth-object: inset 0 1px 0 var(--bevel-highlight), inset 0 -1px 0 var(--bevel-shade), 0 0 0 1px var(--bevel-material-edge), 0 1px 1px var(--bevel-cast), 0 6px 12px -2px var(--bevel-cast), 0 18px 32px -8px var(--bevel-cast); /* The shadow under a loose object, for filter: drop-shadow(). */ --bevel-depth-cast-sm: 0 4px 6px var(--bevel-cast); /* A thin object (a card) lying on another surface. */ --bevel-depth-sheet: inset 0 1px 0 var(--bevel-highlight-strong), inset 0 0 0 1px oklch(0 0 0 / 0.12), 0 -1px 2px oklch(0 0 0 / 0.18); /* 4. Motion */ --bevel-ease-out: cubic-bezier(0.23, 1, 0.32, 1); --bevel-ease-in-out: cubic-bezier(0.77, 0, 0.175, 1); /* Underdamped spring, ~8% overshoot. Pure CSS; pair with --bevel-duration-move or longer. */ --bevel-ease-spring: linear(0, 0.06, 0.202, 0.38, 0.561, 0.723, 0.856, 0.955, 1.023, 1.062, 1.081, 1.083, 1.075, 1.062, 1.047, 1.032, 1.019, 1.009, 1.002, 0.997, 0.994, 0.993, 0.993, 0.994, 1); --bevel-duration-press: 100ms; /* a control giving under a finger */ --bevel-duration-small: 200ms; /* hover lifts, tabs, small state changes */ --bevel-duration-move: 300ms; /* objects moving: cards, drawers */ --bevel-duration-ambient: 520ms; /* whole-scene changes: lighting */ --bevel-stagger: 30ms; --bevel-lift: 3px; /* how far an object rises on hover */ --bevel-perspective: 30rem; /* viewing distance for parts that turn in 3D (covers, flaps) */ /* Unchosen items in a group sit back in shadow. */ --bevel-dim: 0.82; /* 5. Shape */ --bevel-radius-xs: 0.25rem; --bevel-radius-sm: 0.5rem; --bevel-radius-md: 0.625rem; --bevel-radius-lg: 1rem; --bevel-radius-xl: 1.5rem; --bevel-stitch-width: 1.5px; --bevel-stitch-inset: 0.375rem; /* Amounts: the figure, its secondary parts (currency, cents), labels, fine print. */ --bevel-amount: 2.25rem; --bevel-amount-minor: 1.25rem; --bevel-label: 0.75rem; --bevel-fine: 0.6875rem; --bevel-tracking-label: 0.08em; /* stamped labels */ --bevel-tracking-print: 0.06em; /* printed marks on cards */ /* Paper and ink */ --bevel-paper: oklch(0.975 0.008 85); --bevel-paper-foreground: oklch(0.28 0.02 260); --bevel-paper-muted: oklch(0.48 0.015 260); --bevel-paper-rule: oklch(0.78 0.04 240 / 0.45); --bevel-paper-margin: oklch(0.68 0.14 25 / 0.45); --bevel-fibre: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='f'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9 .35' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .18 0 0 0 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23f)'/%3E%3C/svg%3E"); /* Ink soaks unevenly: mostly solid with pale specks. Used as a mask. */ --bevel-ink-texture: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='i'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -2.4 0 0 0 2.1'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23i)'/%3E%3C/svg%3E"); --bevel-ink-red: oklch(0.52 0.19 27); --bevel-ink-blue: oklch(0.44 0.14 262); --bevel-ink-green: oklch(0.5 0.12 155); --bevel-ink-black: oklch(0.3 0.01 260); --bevel-ink-amber: oklch(0.55 0.13 65); /* Brass and ivory: instrument hardware and dial faces */ --bevel-brass: oklch(0.76 0.11 82); --bevel-brass-edge: oklch(0.52 0.08 70); --bevel-ivory: oklch(0.95 0.025 90); --bevel-ivory-foreground: oklch(0.25 0.015 60); --bevel-needle: oklch(0.55 0.2 27); /* Desk stationery: legal pads, pens, pencils, exhibit stickers */ --bevel-pad: oklch(0.95 0.075 98); /* yellow legal pad */ --bevel-pad-foreground: oklch(0.27 0.03 255); --bevel-pad-rule: oklch(0.72 0.06 235 / 0.45); --bevel-pad-margin: oklch(0.62 0.16 25 / 0.55); --bevel-pen-red: oklch(0.52 0.2 27); /* correcting pen */ --bevel-pen-blue: oklch(0.42 0.15 262); /* writing pen */ --bevel-pencil: oklch(0.55 0.01 260); /* not written in ink yet */ --bevel-sticker: oklch(0.9 0.12 95); /* exhibit sticker */ --bevel-sticker-foreground: oklch(0.26 0.03 70); /* Handwriting. Bring a marker font (e.g. Permanent Marker) for the full effect; these are fallbacks. */ --bevel-font-marker: "Permanent Marker", "Marker Felt", "Chalkboard SE", ui-rounded, "Arial Rounded MT Bold", system-ui, sans-serif; } .dark { --bevel-highlight: oklch(1 0 0 / 0.14); /* Paper under dim light, not inverted: it's still paper. */ --bevel-paper: oklch(0.88 0.008 85); --bevel-ivory: oklch(0.86 0.022 90); --bevel-pad: oklch(0.86 0.07 98); --bevel-cast: oklch(0 0 0 / 0.55); } /* Depth */ @utility bevel-recessed { background-color: var(--bevel-material-recess); box-shadow: var(--bevel-depth-recessed); } @utility bevel-flush { box-shadow: var(--bevel-depth-flush); } @utility bevel-raised { background-color: color-mix(in oklch, var(--bevel-material), white 7%); background-image: linear-gradient(to bottom, var(--bevel-highlight), transparent 60%); box-shadow: var(--bevel-depth-raised); } @utility bevel-pressed { background-color: color-mix(in oklch, var(--bevel-material), black 4%); background-image: none; box-shadow: var(--bevel-depth-pressed); } @utility bevel-object { box-shadow: var(--bevel-depth-object); } /* Materials */ @utility bevel-leather { background-color: var(--bevel-material); background-image: var(--bevel-grain), linear-gradient(to bottom, var(--bevel-highlight), transparent 28%, transparent 72%, var(--bevel-shade)); background-size: var(--bevel-grain-size) var(--bevel-grain-size), 100% 100%; background-blend-mode: soft-light, normal; } @utility bevel-plastic { background-color: var(--bevel-material); background-image: linear-gradient(115deg, var(--bevel-highlight-strong), transparent 40%), linear-gradient(to bottom, transparent 25%, oklch(0 0 0 / 0.45)); box-shadow: var(--bevel-depth-sheet); } @utility bevel-metal { background-color: var(--bevel-material); background-image: linear-gradient(to bottom, var(--bevel-highlight-strong), transparent 45%, var(--bevel-shade) 100%), repeating-linear-gradient(90deg, oklch(1 0 0 / 0.06) 0 1px, transparent 1px 3px); box-shadow: inset 0 0 0 1px oklch(0 0 0 / 0.25), inset 0 1px 0 var(--bevel-highlight-strong); } /* Details */ @utility bevel-deboss { text-shadow: var(--bevel-deboss); } @utility bevel-stitch { border-color: var(--bevel-stitch, oklch(0.88 0.05 82 / 0.8)); border-style: dashed; border-width: var(--bevel-stitch-width); filter: drop-shadow(0 1px 0 var(--bevel-shade)); } /* A lit fill sitting in a recess (meters, progress). Colour from --bevel-fill. */ @utility bevel-fill { background-color: var(--bevel-fill); background-image: linear-gradient(to bottom, oklch(1 0 0 / 0.55), transparent 55%, oklch(0 0 0 / 0.18)); box-shadow: 0 0 6px -1px var(--bevel-fill); } /* A small indicator light. Colour from --bevel-led. */ @utility bevel-led { background-color: var(--bevel-led); background-image: radial-gradient(circle at 35% 30%, oklch(1 0 0 / 0.85), transparent 45%); box-shadow: 0 0 6px var(--bevel-led), inset 0 -1px 1px oklch(0 0 0 / 0.35), 0 1px 0 var(--bevel-highlight); } /* Paper: a sheet resting on the surface, with fibre in it. */ @utility bevel-paper { background-color: var(--bevel-paper); background-image: var(--bevel-fibre); background-size: 240px 240px; color: var(--bevel-paper-foreground); box-shadow: 0 1px 1px var(--bevel-cast), 0 6px 14px -4px var(--bevel-cast); } /* Ink on paper: multiplies into the surface and soaks unevenly. Colour from --bevel-ink. */ @utility bevel-ink { color: var(--bevel-ink, var(--bevel-ink-red)); border-color: currentColor; mix-blend-mode: multiply; mask-image: var(--bevel-ink-texture); mask-size: 160px 160px; } /* Glass over an instrument window: one reflection from the light above. Put on an overlay element. */ @utility bevel-glass { pointer-events: none; background-image: linear-gradient(165deg, var(--bevel-highlight-strong) 0%, transparent 38%, transparent 70%, var(--bevel-shade) 100%); } /* Material families: one per screen. Set on the screen's container. */ [data-bevel-family="leather"] { --bevel-cast: oklch(0.25 0.03 50 / 0.22); } [data-bevel-family="paper"] { --bevel-cast: oklch(0.3 0.01 250 / 0.16); } .dark :is([data-bevel-family="leather"], [data-bevel-family="paper"]) { --bevel-cast: oklch(0 0 0 / 0.55); } /* * Ruled paper: rules every --bevel-rule-height and a margin line at * --bevel-rule-margin. Set --bevel-sheet for the paper colour. The ruling * scrolls with the content. */ @utility bevel-ruled { background-color: var(--bevel-sheet, var(--bevel-paper)); background-image: linear-gradient( to right, transparent calc(var(--bevel-rule-margin) - 1px), var(--bevel-sheet-margin, var(--bevel-paper-margin)) calc(var(--bevel-rule-margin) - 1px) calc(var(--bevel-rule-margin) + 1px), transparent calc(var(--bevel-rule-margin) + 1px) ), repeating-linear-gradient( to bottom, transparent 0 calc(var(--bevel-rule-height) - 1px), var(--bevel-sheet-rule, var(--bevel-paper-rule)) calc(var(--bevel-rule-height) - 1px) var(--bevel-rule-height) ), var(--bevel-fibre); background-size: 100% 100%, 100% 100%, 240px 240px; background-attachment: local; color: var(--bevel-paper-foreground); box-shadow: 0 1px 1px var(--bevel-cast), 0 6px 14px -4px var(--bevel-cast); }Install the components Gauge and dial is built from: Amount.
Copy
dial.tsxintocomponents/ui.components/dial.tsx "use client" import * as React from "react" import { Meter as MeterPrimitive } from "@base-ui/react/meter" import { cn } from "cn" import { formatAmount } from "@/lib/format-amount" /* The needle sweeps 270°, from bottom-left (-135°) to bottom-right (135°). */ const SWEEP = 270 const START = -SWEEP / 2 type Scale = { min: number; max: number } const ScaleContext = React.createContext<Scale & { value: number }>({ min: 0, max: 100, value: 0 }) function angleFor(value: number, { min, max }: Scale) { const t = max === min ? 0 : (Math.min(Math.max(value, min), max) - min) / (max - min) return START + t * SWEEP } /** Ticks, marks, needle and glass. Shared by Gauge and Dial. */ function Face({ ticks, dragging, children, }: { ticks: number dragging?: boolean children?: React.ReactNode }) { const { value, ...scale } = React.useContext(ScaleContext) return ( <span data-slot="dial-face" className="bevel-metal relative block aspect-square w-full rounded-full p-(--bevel-stitch-inset) shadow-(--bevel-depth-object)" > <span className="relative block size-full overflow-hidden rounded-full bg-(--bevel-ivory) text-(--bevel-ivory-foreground) shadow-(--bevel-depth-recessed)"> <svg viewBox="0 0 100 100" aria-hidden className="absolute inset-0 size-full"> {Array.from({ length: ticks + 1 }, (_, i) => { const major = i % 5 === 0 || i === ticks return ( <line key={i} x1="50" y1={major ? 9 : 11} x2="50" y2="16" stroke="currentColor" strokeWidth={major ? 1.6 : 0.8} transform={`rotate(${START + (i / ticks) * SWEEP} 50 50)`} /> ) })} </svg> {children} <span aria-hidden data-slot="dial-needle" data-dragging={dragging ? "" : undefined} className="absolute inset-0 transition-[rotate] duration-(--bevel-duration-ambient) ease-(--bevel-ease-spring) data-dragging:duration-0 motion-reduce:transition-none" style={{ rotate: `${angleFor(value, scale)}deg` }} > <svg viewBox="0 0 100 100" className="size-full drop-shadow-(--bevel-depth-cast-sm)"> <path d="M50 12 L52 50 L48 50 Z" fill="var(--bevel-needle)" /> <path d="M48 50 L52 50 L51 60 L49 60 Z" fill="currentColor" /> </svg> </span> <span aria-hidden className="bevel-metal absolute top-1/2 left-1/2 size-[12%] -translate-1/2 rounded-full" /> <span aria-hidden className="bevel-glass absolute inset-0 rounded-full" /> </span> </span> ) } type DialMarkProps = Omit<React.ComponentProps<"span">, "children"> & { value: number tone?: "red" | "amber" | "green" } /** A painted mark on the scale: a threshold, a limit, a target. Decorative; describe it in text too. */ function DialMark({ value, tone = "red", className, style, ...props }: DialMarkProps) { const scale = React.useContext(ScaleContext) return ( <span aria-hidden data-slot="dial-mark" data-tone={tone} className={cn("absolute inset-0", className)} style={{ rotate: `${angleFor(value, scale)}deg`, ...style }} {...props} > <svg viewBox="0 0 100 100" className="size-full"> <path d="M48.2 6 L51.8 6 L51 18 L49 18 Z" fill="var(--dial-mark)" /> </svg> </span> ) } type GaugeProps = MeterPrimitive.Root.Props & { /** Number of tick intervals on the scale. */ ticks?: number /** Marks (DialMark) painted on the face. */ marks?: React.ReactNode } /** A read-only instrument: the needle shows a value on a scale. Announced as a meter. */ function Gauge({ className, value, min = 0, max = 100, ticks = 20, marks, children, ...props }: GaugeProps) { return ( <ScaleContext value={{ min, max, value }}> <MeterPrimitive.Root data-slot="gauge" value={value} min={min} max={max} className={cn("flex w-48 flex-col items-center gap-3 text-center", className)} {...props} > <Face ticks={ticks}>{marks}</Face> {children} </MeterPrimitive.Root> </ScaleContext> ) } function GaugeLabel({ className, ...props }: MeterPrimitive.Label.Props) { return ( <MeterPrimitive.Label data-slot="gauge-label" className={cn("text-sm text-muted-foreground", className)} {...props} /> ) } function GaugeValue({ className, ...props }: MeterPrimitive.Value.Props) { return ( <MeterPrimitive.Value data-slot="gauge-value" className={cn("text-lg font-semibold tabular-nums", className)} {...props} /> ) } type DialProps = Omit<React.ComponentProps<"div">, "defaultValue" | "onChange" | "children"> & { value?: number defaultValue?: number onValueChange?: (value: number) => void min?: number max?: number step?: number /** Number of tick intervals on the scale. */ ticks?: number /** Formats the value for the readout and for screen readers. */ currency?: string format?: Intl.NumberFormatOptions locale?: Intl.LocalesArgument /** Marks (DialMark) painted on the face. */ marks?: React.ReactNode /** A name for the control, shown below the dial. */ label: React.ReactNode disabled?: boolean } /** * An adjustable dial: turn it with a drag, or arrow keys, Page Up/Down, Home and End. * Announced as a slider. */ function Dial({ className, value: valueProp, defaultValue, onValueChange, min = 0, max = 100, step = 1, ticks = 20, currency, format, locale, marks, label, disabled, ...props }: DialProps) { const [uncontrolled, setUncontrolled] = React.useState(defaultValue ?? min) const value = valueProp ?? uncontrolled const [dragging, setDragging] = React.useState(false) const labelId = React.useId() const text = formatAmount({ value, currency, format, locale }) const commit = (next: number) => { const snapped = Math.min(max, Math.max(min, Math.round((next - min) / step) * step + min)) if (snapped === value) return if (valueProp === undefined) setUncontrolled(snapped) onValueChange?.(snapped) } const fromPointer = (event: React.PointerEvent<HTMLDivElement>) => { const rect = event.currentTarget.getBoundingClientRect() const dx = event.clientX - (rect.left + rect.width / 2) const dy = event.clientY - (rect.top + rect.height / 2) // 0° points up; clockwise is positive. Below the scale, snap to the nearer end. let angle = (Math.atan2(dx, -dy) * 180) / Math.PI if (angle > SWEEP / 2) angle = SWEEP / 2 if (angle < -SWEEP / 2) angle = -SWEEP / 2 commit(min + ((angle - START) / SWEEP) * (max - min)) } const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => { const big = Math.max(step, (max - min) / 10) const next = { ArrowUp: value + step, ArrowRight: value + step, ArrowDown: value - step, ArrowLeft: value - step, PageUp: value + big, PageDown: value - big, Home: min, End: max, }[event.key] if (next === undefined) return event.preventDefault() commit(next) } return ( <ScaleContext value={{ min, max, value }}> <div data-slot="dial" data-disabled={disabled ? "" : undefined} className={cn( "flex w-48 flex-col items-center gap-3 text-center data-disabled:opacity-60", className )} {...props} > <div role="slider" tabIndex={disabled ? -1 : 0} aria-labelledby={labelId} aria-valuemin={min} aria-valuemax={max} aria-valuenow={value} aria-valuetext={text} aria-disabled={disabled || undefined} data-slot="dial-control" className="w-full cursor-grab touch-none rounded-full outline-none select-none focus-visible:ring-3 focus-visible:ring-ring/60 focus-visible:ring-offset-2 focus-visible:ring-offset-background active:cursor-grabbing aria-disabled:cursor-not-allowed" onKeyDown={disabled ? undefined : onKeyDown} onPointerDown={(event) => { if (disabled || event.button !== 0) return event.currentTarget.setPointerCapture(event.pointerId) setDragging(true) fromPointer(event) }} onPointerMove={(event) => { if (dragging) fromPointer(event) }} onPointerUp={() => setDragging(false)} onPointerCancel={() => setDragging(false)} > <Face ticks={ticks} dragging={dragging}> {marks} </Face> </div> <span id={labelId} data-slot="dial-label" className="text-sm text-muted-foreground"> {label} </span> <span aria-hidden data-slot="dial-value" className="text-lg font-semibold tabular-nums"> {text} </span> </div> </ScaleContext> ) } export { Gauge, GaugeLabel, GaugeValue, Dial, DialMark } export type { GaugeProps, DialProps, DialMarkProps }Copy
dial.cssintoyour styles.styles/dial.css /* * Gauge and Dial: a needle over an ivory face in a brass bezel. Builds on * bevel.css (brass, ivory, needle, glass, motion). */ :root { --dial-mark-red: oklch(0.58 0.2 27); --dial-mark-amber: oklch(0.75 0.16 70); --dial-mark-green: oklch(0.6 0.15 150); } [data-slot="gauge"], [data-slot="dial"] { --bevel-material: var(--bevel-brass); --bevel-material-edge: var(--bevel-brass-edge); } [data-slot="dial-mark"] { --dial-mark: var(--dial-mark-red); } [data-slot="dial-mark"][data-tone="amber"] { --dial-mark: var(--dial-mark-amber); } [data-slot="dial-mark"][data-tone="green"] { --dial-mark: var(--dial-mark-green); }Import the stylesheets after Tailwind in your global CSS, foundation first.
globals.css @import "./bevel.css"; @import "./dial.css";
Anatomy
Each part is its own component. Leave out the ones you don't need.
<Gauge value={12} max={30} marks={<DialMark value={5} />}>
<GaugeLabel />
<GaugeValue />
</Gauge>
<Dial label="Keep above" value={30} onValueChange={setValue} />Parts
| Part | Renders | What it does |
|---|---|---|
| Gauge | Meter | Read-only. value, min, max, ticks, marks. Announced as a meter; pass getAriaValueText to describe marks. |
| GaugeLabel / GaugeValue | Meter parts | The name and the formatted value. |
| Dial | div (slider) | Adjustable. value, defaultValue, onValueChange, min, max, step, label, and Amount formatting for the readout. Arrows, Page Up/Down, Home, End. |
| DialMark | span | A painted mark on the scale: red, amber or green. Decorative. |