Review and approveDesign tuner
Built by DesignMe, free to use under the MIT licence.
Expert design and dev support for startups and enterprises.
Book a callDesign tuner
Tweak a component by hand or let the agent tune it. Free and MIT licensed, as HTML or a typed React component, in two styles with light and dark mode.
Install
Add it to your project with the CLI.
HTML, CSS and JavaScript
npx designme add design-tunerReact
npx designme add design-tuner --reactThe CLI copies the source into your project, so you own it and can change anything. The HTML version lands in ./designme with the shared core and a demo.html. The React version lands in src/components/designme and needs React 19; it works in Next.js, Vite and Astro.
About
What it is for and what it does.
Design tuner puts a live preview next to a few sliders, such as radius, padding, spacing and shadow, so a person can tweak a component by hand. A button hands the same sliders to the agent, which can move them while people watch and then explain what it changed.
- When people and the agent should share control over a component's look.
- When you want the agent's styling choices to be visible as slider moves, not hidden in code.
- When designers want to fine-tune spacing or radius after the agent has made a first pass.
- When you need a short note from the agent about why it picked the new values.
- Each entry in
paramsbecomes a slider with a label, a range, a unit like px, and optionaldecimalsfor fine values. - Every value is set on the preview as a CSS variable named
--dm-<key>, so your styles can read it. previewlets you render your own component. It receives the values and a style object with those variables. Without it, a sample pricing card is shown.activehighlights the slider the agent is moving, andstatusshows a short line like "Agent is tuning".- The tune button calls
onTune. Whiletuningis true it shows a thinking animation and ignores clicks. noteshows the agent's explanation above the button, andactionLabellets you change it to something like "Tune again".
- Each slider has
role="slider", is focusable, and setsaria-label,aria-valuemin,aria-valuemax,aria-valuenowandaria-valuetext. - Arrow keys move a slider by one step, or by the smallest decimal when
decimalsis set. - The tune button sets
aria-busywhile the agent is tuning.
Usage
Import it and drive it from your own state.
Import
import { DesignTuner } from '@/components/designme/design-tuner/DesignTuner';Example
The demo above as a React component, 41 lines you can copy.
'use client';
// The animated preview from designme.dev, rebuilt from the React component: an example of driving it from your own state.
import { useEffect, useRef, useState } from 'react';
import { DesignTuner } from './DesignTuner';
import { useScript, useTakeover } from '../lib/useScript';
/* ---------- Design tuner: drag the sliders, or let the agent tune each one in turn ---------- */
const START = { rad: 16, pad: 18, gap: 12, sh: .08 };
const KEYS = ['rad', 'pad', 'gap', 'sh'] as const;
export default function DesignTunerDemo() {
const [values, setValues] = useState<Record<string, number>>(START);
const [status, setStatus] = useState('Edited by you');
const [active, setActive] = useState<string>();
const [note, setNote] = useState<string>();
const [runs, setRuns] = useState(0);
const live = useRef(values), ref = useRef<HTMLDivElement>(null), mine = useTakeover(ref);
useEffect(() => { live.current = values; });
// the agent tunes once on its own, unless you take over first
useScript(async ({ wait }) => { await wait(1600); if (!mine()) setRuns(r => r || 1); });
// each run aims at the other set of values, so Tune again always has something to do
useScript(async ({ wait }) => {
if (runs <= 0) return;
const target = runs % 2 ? { rad: 20, pad: 24, gap: 14, sh: .05 } : { rad: 12, pad: 20, gap: 10, sh: .1 };
setStatus('Agent is tuning'); setNote(undefined);
for (const k of KEYS) {
setActive(k);
const from = live.current[k], to = target[k], t0 = performance.now();
for (let p = 0; p < 1;) { await wait(16); p = Math.min(1, (performance.now() - t0) / 650); setValues(v => ({ ...v, [k]: from + (to - from) * (1 - Math.pow(1 - p, 3)) })); }
await wait(120); setActive(undefined);
}
setNote('Matched the 4px grid and your card tokens. Softer shadow so the CTA leads.');
setStatus('Tuned by agent'); setRuns(r => -r);
}, [runs > 0 ? runs : 0]);
return (
<div ref={ref} style={{ width: '100%' }}>
<DesignTuner values={values} onValuesChange={v => { setValues(v); setStatus('Edited by you'); }} status={status} active={active}
tuning={runs > 0} note={note} actionLabel={runs ? 'Tune again' : 'Tune with agent'} onTune={() => setRuns(r => Math.abs(r) + 1)} />
</div>
);
}
Props
Everything DesignTuner takes.
| Prop | Type | What it does |
|---|---|---|
params | TunerParam[] | The sliders. Defaults to radius, padding, spacing and shadow for the sample card |
values | Record<string, number> | The value of each slider by key. Leave it out to let the sliders keep their own |
defaultValues | Record<string, number> | |
onValuesChange | (values: Record<string, number>) => void | |
title | string | What is being tuned, like "Pricing card" |
status | string | Quiet line beside the title, like "Edited by you" or "Agent is tuning" |
active | string | Key of the slider the agent is moving now; its value turns the accent colour |
tuning | boolean | The agent is tuning: the button thinks and stops taking clicks |
note | ReactNode | What the agent changed and why, shown above the button |
actionLabel | string | The button's label, like "Tune with agent" or "Tune again" |
onTune | () => void | |
preview | (values: Record<string, number>, vars: CSSProperties) => ReactNode | What the sliders change. Gets the values and a style with a `--dm-<key>` variable for each one. Defaults to the sample pricing card from designme.dev |
className | string |
Code
The full source, as the CLI installs it.
DesignTuner.tsxReact · 123 lines
'use client';
import { useRef, useState, type CSSProperties, type KeyboardEvent, type PointerEvent, type ReactNode } from 'react';
import { cx } from '../lib/cx';
import { useControllable } from '../lib/hooks';
import { Icon } from '../lib/icons';
import { enter, Swap, Think } from '../lib/motion';
import '../designme.css';
import './design-tuner.css';
export interface TunerParam {
/** Names the value and the CSS variable it sets on the preview, `--dm-<key>` */
key: string;
/** Shown beside the slider, like "Radius" */
label: string;
min: number;
max: number;
/** Where the slider starts when you don't pass values */
value: number;
/** Added after whole numbers, like "px". Leave it out for plain numbers */
unit?: string;
/** Shows and sets the value with this many decimals instead of rounding it, like 2 for a shadow strength */
decimals?: number;
}
export interface DesignTunerProps {
/** The sliders. Defaults to radius, padding, spacing and shadow for the sample card */
params?: TunerParam[];
/** The value of each slider by key. Leave it out to let the sliders keep their own */
values?: Record<string, number>;
defaultValues?: Record<string, number>;
onValuesChange?: (values: Record<string, number>) => void;
/** What is being tuned, like "Pricing card" */
title?: string;
/** Quiet line beside the title, like "Edited by you" or "Agent is tuning" */
status?: string;
/** Key of the slider the agent is moving now; its value turns the accent colour */
active?: string;
/** The agent is tuning: the button thinks and stops taking clicks */
tuning?: boolean;
/** What the agent changed and why, shown above the button */
note?: ReactNode;
/** The button's label, like "Tune with agent" or "Tune again" */
actionLabel?: string;
onTune?: () => void;
/** What the sliders change. Gets the values and a style with a `--dm-<key>` variable for each one.
Defaults to the sample pricing card from designme.dev */
preview?: (values: Record<string, number>, vars: CSSProperties) => ReactNode;
className?: string;
}
const PARAMS: TunerParam[] = [
{ key: 'rad', label: 'Radius', min: 0, max: 28, value: 16, unit: 'px' },
{ key: 'pad', label: 'Padding', min: 8, max: 32, value: 18, unit: 'px' },
{ key: 'gap', label: 'Spacing', min: 4, max: 24, value: 12, unit: 'px' },
{ key: 'sh', label: 'Shadow', min: 0, max: .2, value: .08, decimals: 2 }
];
const clamp = (p: TunerParam, v: number) => Math.max(p.min, Math.min(p.max, v));
const shown = (p: TunerParam, v: number) => p.decimals ? v.toFixed(p.decimals) : Math.round(v) + (p.unit ?? '');
const cssValue = (p: TunerParam, v: number) => p.decimals ? String(+v.toFixed(p.decimals)) : Math.round(v) + (p.unit ?? '');
/** The sample the site tunes: a pricing card that reads --dm-rad, --dm-pad, --dm-gap and --dm-sh */
function SampleCard({ vars }: { vars: CSSProperties }) {
return (
<div className="dm-dtx-card" style={vars}>
<span className="dm-tag dm-b dm-dtx-tg">Pro</span>
<div className="dm-dtx-pr"><b className="dm-num">$24</b><span className="dm-muted">per editor, monthly</span></div>
<div className="dm-dtx-li">{['Unlimited frames', 'Agent reviews', 'Shared tokens'].map(t => <span key={t}><Icon name="check" size="xs" />{t}</span>)}</div>
<button type="button" className="dm-pill dm-accent dm-dtx-cta">Start free trial</button>
</div>
);
}
function Slider({ param, value, hot, onChange }: { param: TunerParam; value: number; hot: boolean; onChange: (v: number) => void }) {
const [drag, setDrag] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const pct = (value - param.min) / (param.max - param.min) * 100;
const at = (e: PointerEvent) => { const b = ref.current!.getBoundingClientRect(); onChange(clamp(param, param.min + (param.max - param.min) * Math.max(0, Math.min(1, (e.clientX - b.left) / b.width)))); };
const key = (e: KeyboardEvent) => {
const dir = ({ ArrowRight: 1, ArrowUp: 1, ArrowLeft: -1, ArrowDown: -1 } as Record<string, number>)[e.key]; if (!dir) return;
e.preventDefault(); onChange(clamp(param, value + dir * (param.decimals ? Math.pow(10, -param.decimals) : 1)));
};
return (
<div className={cx('dm-dtx-r', hot && 'dm-hot')}>
<span>{param.label}</span>
<div ref={ref} className={cx('dm-dtx-sl', drag && 'dm-drag')} role="slider" tabIndex={0} aria-label={param.label}
aria-valuemin={param.min} aria-valuemax={param.max} aria-valuenow={param.decimals ? +value.toFixed(param.decimals) : Math.round(value)} aria-valuetext={shown(param, value)}
onPointerDown={e => { e.currentTarget.setPointerCapture(e.pointerId); setDrag(true); at(e); }}
onPointerMove={e => { if (drag) at(e); }}
onPointerUp={() => setDrag(false)} onPointerCancel={() => setDrag(false)} onKeyDown={key}>
<span className="dm-f" style={{ width: pct + '%' }} /><span className="dm-k" style={{ left: pct + '%' }} />
</div>
<span className="dm-v dm-num">{shown(param, value)}</span>
</div>
);
}
/** A live preview beside a few sliders, with a button that hands the tuning to the agent. */
export function DesignTuner({ params = PARAMS, values, defaultValues, onValuesChange, title = 'Pricing card', status = 'Edited by you', active, tuning = false, note,
actionLabel = 'Tune with agent', onTune, preview, className }: DesignTunerProps) {
const [vals, setVals] = useControllable(values, defaultValues ?? Object.fromEntries(params.map(p => [p.key, p.value])), onValuesChange);
const valueOf = (p: TunerParam) => clamp(p, vals[p.key] ?? p.value);
const vars = Object.fromEntries(params.map(p => ['--dm-' + p.key, cssValue(p, valueOf(p))])) as CSSProperties;
return (
// the site lays this out by the width of its card, so the box around it is the container its query measures
<div data-dm="" className={cx('dm-dtx-box', className)} style={{ containerType: 'inline-size', width: '100%' }}>
<div className="dm-dtx">
<div className="dm-dtx-prev">{preview ? preview(Object.fromEntries(params.map(p => [p.key, valueOf(p)])), vars) : <SampleCard vars={vars} />}</div>
<div className="dm-dtx-insp dm-panel">
<div className="dm-dtx-ih"><b>{title}</b><Swap className="dm-muted dm-dtx-st">{status}</Swap></div>
<div className="dm-dtx-rows">
{params.map(p => <Slider key={p.key} param={p} value={valueOf(p)} hot={active === p.key} onChange={v => setVals({ ...vals, [p.key]: v })} />)}
</div>
{note && <div className="dm-dtx-note dm-enter" style={enter().style}>{note}</div>}
<button type="button" className={cx('dm-pill dm-primary dm-sm dm-dtx-go', tuning && 'dm-busy')} aria-busy={tuning} onClick={() => { if (!tuning) onTune?.(); }}>
{tuning ? <><Think />Tuning</> : <><Icon name="wand" size="xs" />{actionLabel}</>}
</button>
</div>
</div>
</div>
);
}
design-tuner.cssReact · 34 lines
/* Generated from designme.dev by scripts/sync.mjs. Edit the site, then sync. */
/* ---------- Design tuner ---------- */
.dm-dtx{display:grid;grid-template-columns:1fr 280px;gap:14px;align-items:stretch}
@media (max-width:720px){.dm-dtx{grid-template-columns:1fr}}
@container (max-width:600px){.dm-dtx{grid-template-columns:1fr}.dm-dtx-prev{min-height:240px}}
.dm-dtx-prev{border-radius:18px;background:var(--dm-chip);display:grid;place-items:center;padding:var(--dm-s-6);min-height:300px;min-width:0}
.dm-dtx-card{--dm-rad:16px;--dm-pad:18px;--dm-gap:12px;--dm-sh:.08;width:min(250px,100%);background:var(--dm-surface);border-radius:var(--dm-rad);padding:var(--dm-pad);display:flex;flex-direction:column;gap:var(--dm-gap);box-shadow:0 1px 2px rgba(0,0,0,var(--dm-sh)),0 8px 24px rgba(0,0,0,calc(var(--dm-sh) * .8))}
.dm-dtx-tg{align-self:flex-start}
.dm-dtx-pr{display:flex;align-items:baseline;gap:6px}
.dm-dtx-pr b{font-size:14px;font-weight:500;letter-spacing:-.02em}
.dm-dtx-pr span{font-size:12px}
.dm-dtx-li{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--dm-fg2)}
.dm-dtx-li span{display:flex;align-items:center;gap:7px}
.dm-dtx-li svg{color:var(--dm-accent-fg,var(--dm-accent))}
.dm-dtx-cta{width:100%;border-radius:calc(var(--dm-rad) * .6)}
.dm-dtx-insp{padding:var(--dm-s-4);display:flex;flex-direction:column;gap:var(--dm-s-3);min-width:0}
.dm-dtx-ih{display:flex;align-items:baseline;justify-content:space-between;font-size:13.5px}
.dm-dtx-ih b{font-weight:500}
.dm-dtx-st{font-size:12px}
.dm-dtx-rows{display:flex;flex-direction:column;gap:10px}
.dm-dtx-r{display:grid;grid-template-columns:62px 1fr 40px;align-items:center;gap:10px;font-size:12.5px;color:var(--dm-fg2)}
.dm-dtx-r .dm-v{text-align:right;color:var(--dm-fg);font-size:12px}
.dm-dtx-r.dm-hot .dm-v{color:var(--dm-accent-fg,var(--dm-accent))}
.dm-dtx-sl{position:relative;height:22px;display:flex;align-items:center;cursor:pointer;touch-action:none}
.dm-dtx-sl::before{content:"";position:absolute;left:0;right:0;height:4px;border-radius:2px;background:var(--dm-line)}
.dm-dtx-sl .dm-f{position:absolute;left:0;height:4px;border-radius:2px;background:var(--dm-accent-mark,var(--dm-accent))}
.dm-dtx-sl .dm-k{position:absolute;width:14px;height:14px;margin-left:-7px;border-radius:50%;background:#fff;box-shadow:0 0 0 1px color-mix(in srgb,var(--dm-line) 50%,transparent),0 1px 3px rgba(0,0,0,.18);transition:transform .2s}
.dm-dtx-sl.dm-drag .dm-k,.dm-dtx-sl:focus-visible .dm-k{transform:scale(1.15)}
@media (hover:hover) and (pointer:fine){.dm-dtx-sl:hover .dm-k{transform:scale(1.15)}}
.dm-dtx-sl:focus-visible{outline-offset:4px}
.dm-dtx-go{align-self:stretch;margin-top:auto}
.dm-dtx-go.dm-busy{pointer-events:none}
.dm-dtx-note{font-size:12px;color:var(--dm-fg2);padding:var(--dm-s-2) 10px;border-radius:10px;background:var(--dm-accent-bg);line-height:1.45}
design-tuner.htmlHTML · 18 lines
<div class="card wide" data-c="design-tuner">
<div class="stage-in">
<div class="dtx">
<div class="dtx-prev"><div class="dtx-card">
<span class="tag b dtx-tg">Pro</span>
<div class="dtx-pr"><b class="num">$24</b><span class="muted">per editor, monthly</span></div>
<div class="dtx-li"><span><svg class="i xs"><use href="#i-check"/></svg>Unlimited frames</span><span><svg class="i xs"><use href="#i-check"/></svg>Agent reviews</span><span><svg class="i xs"><use href="#i-check"/></svg>Shared tokens</span></div>
<button class="pill accent dtx-cta">Start free trial</button>
</div></div>
<div class="dtx-insp panel">
<div class="dtx-ih"><b>Pricing card</b><span class="muted dtx-st">Edited by you</span></div>
<div class="dtx-rows"></div>
<button class="pill primary sm dtx-go"><svg class="i xs"><use href="#i-wand"/></svg>Tune with agent</button>
</div>
</div>
</div>
</div>
design-tuner.cssHTML · 33 lines
/* ---------- Design tuner ---------- */
.dtx{display:grid;grid-template-columns:1fr 280px;gap:14px;align-items:stretch}
@media (max-width:720px){.dtx{grid-template-columns:1fr}}
@container (max-width:600px){.dtx{grid-template-columns:1fr}.dtx-prev{min-height:240px}}
.dtx-prev{border-radius:18px;background:var(--chip);display:grid;place-items:center;padding:var(--s-6);min-height:300px;min-width:0}
.dtx-card{--rad:16px;--pad:18px;--gap:12px;--sh:.08;width:min(250px,100%);background:var(--surface);border-radius:var(--rad);padding:var(--pad);display:flex;flex-direction:column;gap:var(--gap);box-shadow:0 1px 2px rgba(0,0,0,var(--sh)),0 8px 24px rgba(0,0,0,calc(var(--sh) * .8))}
.dtx-tg{align-self:flex-start}
.dtx-pr{display:flex;align-items:baseline;gap:6px}
.dtx-pr b{font-size:14px;font-weight:500;letter-spacing:-.02em}
.dtx-pr span{font-size:12px}
.dtx-li{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--fg2)}
.dtx-li span{display:flex;align-items:center;gap:7px}
.dtx-li svg{color:var(--accent-fg,var(--accent))}
.dtx-cta{width:100%;border-radius:calc(var(--rad) * .6)}
.dtx-insp{padding:var(--s-4);display:flex;flex-direction:column;gap:var(--s-3);min-width:0}
.dtx-ih{display:flex;align-items:baseline;justify-content:space-between;font-size:13.5px}
.dtx-ih b{font-weight:500}
.dtx-st{font-size:12px}
.dtx-rows{display:flex;flex-direction:column;gap:10px}
.dtx-r{display:grid;grid-template-columns:62px 1fr 40px;align-items:center;gap:10px;font-size:12.5px;color:var(--fg2)}
.dtx-r .v{text-align:right;color:var(--fg);font-size:12px}
.dtx-r.hot .v{color:var(--accent-fg,var(--accent))}
.dtx-sl{position:relative;height:22px;display:flex;align-items:center;cursor:pointer;touch-action:none}
.dtx-sl::before{content:"";position:absolute;left:0;right:0;height:4px;border-radius:2px;background:var(--line)}
.dtx-sl .f{position:absolute;left:0;height:4px;border-radius:2px;background:var(--accent-mark,var(--accent))}
.dtx-sl .k{position:absolute;width:14px;height:14px;margin-left:-7px;border-radius:50%;background:#fff;box-shadow:0 0 0 1px color-mix(in srgb,var(--line) 50%,transparent),0 1px 3px rgba(0,0,0,.18);transition:transform .2s}
.dtx-sl.drag .k,.dtx-sl:focus-visible .k{transform:scale(1.15)}
@media (hover:hover) and (pointer:fine){.dtx-sl:hover .k{transform:scale(1.15)}}
.dtx-sl:focus-visible{outline-offset:4px}
.dtx-go{align-self:stretch;margin-top:auto}
.dtx-go.busy{pointer-events:none}
.dtx-note{font-size:12px;color:var(--fg2);padding:var(--s-2) 10px;border-radius:10px;background:var(--accent-bg);line-height:1.45}
design-tuner.jsHTML · 32 lines
DM.def('design-tuner', (r, c) => {
r.innerHTML = r.innerHTML.replace(/\{c\}/g, I('check','xs')).replace('{wand}', I('wand','xs'));
const card = $('.dtx-card', r), rows = $('.dtx-rows', r), st = $('.dtx-st', r), go = $('.dtx-go', r);
const P = [
{ k:'rad', n:'Radius', min:0, max:28, v:16, u:'px', t:20 },
{ k:'pad', n:'Padding', min:8, max:32, v:18, u:'px', t:24 },
{ k:'gap', n:'Spacing', min:4, max:24, v:12, u:'px', t:14 },
{ k:'sh', n:'Shadow', min:0, max:.2, v:.08, u:'', t:.05, f:2 }
];
rows.innerHTML = P.map(p => `<div class="dtx-r" data-k="${p.k}"><span>${p.n}</span><div class="dtx-sl" role="slider" tabindex="0" aria-label="${p.n}" aria-valuemin="${p.min}" aria-valuemax="${p.max}"><span class="f"></span><span class="k"></span></div><span class="v num"></span></div>`).join('');
function set(p, v){ p.v = Math.max(p.min, Math.min(p.max, v)); const pc = (p.v - p.min) / (p.max - p.min) * 100; const row = $(`[data-k="${p.k}"]`, rows);
$('.f', row).style.width = pc + '%'; $('.k', row).style.left = pc + '%'; $('.v', row).textContent = p.f ? p.v.toFixed(p.f) : Math.round(p.v) + p.u;
card.style.setProperty('--' + p.k, p.f ? p.v : Math.round(p.v) + 'px'); $('.dtx-sl', row).setAttribute('aria-valuenow', p.f ? p.v.toFixed(p.f) : Math.round(p.v)); }
P.forEach(p => {
set(p, p.v); const sl = $(`[data-k="${p.k}"] .dtx-sl`, rows);
const at = e => { const b = sl.getBoundingClientRect(); set(p, p.min + (p.max - p.min) * Math.max(0, Math.min(1, (e.clientX - b.left) / b.width))); };
sl.onpointerdown = e => { sl.setPointerCapture(e.pointerId); sl.classList.add('drag'); at(e); swap(st, 'Edited by you'); sl.onpointermove = at; };
sl.onpointerup = sl.onpointercancel = () => { sl.classList.remove('drag'); sl.onpointermove = null; };
sl.onkeydown = e => { const k = { ArrowRight:1, ArrowUp:1, ArrowLeft:-1, ArrowDown:-1 }[e.key]; if (!k) return; e.preventDefault(); set(p, p.v + k * (p.f ? .01 : 1)); swap(st, 'Edited by you'); };
});
let note;
go.onclick = () => c.run(async () => {
go.classList.add('busy'); go.innerHTML = '<span class="think on"><i></i><i></i><i></i><i></i></span>Tuning'; swap(st, 'Agent is tuning'); if (note) note.remove();
for (const p of P){ const row = $(`[data-k="${p.k}"]`, rows); row.classList.add('hot'); await tween(p.v, p.t, 650, x => set(p, x)); await c.wait(120); row.classList.remove('hot'); }
note = html('<div class="dtx-note">Matched the 4px grid and your card tokens. Softer shadow so the CTA leads.</div>'); go.before(note); enter(note);
go.classList.remove('busy'); go.innerHTML = I('wand','xs') + 'Tune again'; swap(st, 'Tuned by agent');
P.forEach(p => { const o = p.t; p.t = p.k === 'rad' ? (o === 20 ? 12 : 20) : p.k === 'pad' ? (o === 24 ? 20 : 24) : p.k === 'gap' ? (o === 14 ? 10 : 14) : (o === .05 ? .1 : .05); });
});
// the preview lets the agent tune once on its own, unless you reach for a slider or the button first
c.auto(1600, () => go.click());
});
Questions
What people ask about it.
- How do I let the agent move the sliders?
- Control
valuesand update them from your agent as it works. Settuningandactivewhile it runs so people can see which slider it is on. - How do I tune my own component instead of the sample card?
- Pass
preview, a function that gets the values and a style object. Spread the style onto your element and read the--dm-<key>variables in its CSS. - Can a slider hold decimal values?
- Yes. Set
decimalson the param, for example 2 for a shadow strength. The value is shown and set with that many decimals instead of being rounded.
More in Review and approve
Moments where a person checks, steers or signs off on the agent's work.