bêta
Angle Dial <mv-angle-dial>
Saisie d’angle en degrés qui se lit comme en CSS (0° en haut, sens horaire) : un bouton rotatif dans un anneau de graduations qui sert aussi de grille d’aimantation, avec les graduations derrière le bouton allumées et qui s’estompent avec la distance. Faites glisser avec de légers crans tous les 45° ou maintenez Maj pour aimanter, clavier complet de slider, associé aux formulaires.
| Catégorie | Formulaires |
|---|---|
| Type | Web Component (<mv-angle-dial>) |
| Statut | bêta |
| Keywords | angle, dial, knob, rotation, gradient, inspector, slider, form-associated, editor, ticks |
When to use
- A design tool lets users set a rotation or gradient direction in degrees by dragging around a dial
- An inspector panel needs a compact angle control that snaps to 15 or 45 degree steps
- A settings form must submit a direction or heading as a plain number of degrees
Avoid when
- The value is a generic linear number rather than an angle → use Slider instead
- Users need to type the exact angle more often than drag it → use Scrub Field instead
- The audience is non-technical and a circular control would be unfamiliar in a regular form
Installation
node scripts/add.mjs angle-dial --out ./src/marvelousAgent IA avec le serveur MCP de Marvelous UI : install_components({ slugs: ["angle-dial"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Fichiers copiés (dépendances comprises) : tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/form.js, components/angle-dial/angle-dial.js, components/angle-dial/angle-dial.css.
Utilisation
Balisage de référence : partez de celui-ci et personnalisez-le avec les attributs, data-* et les variables CSS :
<div id="angle-demo">
<style>
#angle-demo { display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: center; justify-content: center; width: 100%; }
#angle-demo .ad-stage { display: grid; place-items: center; width: 15rem; height: 15rem; border-radius: var(--mv-radius-xl); background: radial-gradient(circle at 1px 1px, color-mix(in oklch, var(--mv-fg) 12%, transparent) 1px, transparent 0) 0 0 / 12px 12px, var(--mv-bg); border: 1px solid var(--mv-border); }
#angle-demo .ad-shape { width: 8rem; height: 8rem; border-radius: 1.5rem; box-shadow: 0 18px 40px -16px oklch(0.4 0.2 260 / .55); }
#angle-demo .ad-panel { display: grid; gap: .6rem; width: 16.5rem; padding: .85rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-lg); background: var(--mv-surface); box-shadow: var(--mv-shadow-sm); }
#angle-demo .ad-head { margin: 0; font: 600 .7rem var(--mv-font-sans); letter-spacing: .06em; text-transform: uppercase; color: var(--mv-fg-subtle); }
#angle-demo .ad-row { display: grid; grid-template-columns: 4.75rem auto 1fr; align-items: center; gap: .6rem; font-size: .75rem; color: var(--mv-fg-muted); }
#angle-demo .ad-row mv-scrub-field { width: 100%; }
#angle-demo .ad-big { --mv-angle-dial-size: 3.5rem; }
#angle-demo .ad-hint { margin: .15rem 0 0; font-size: .7rem; line-height: 1.5; color: var(--mv-fg-muted); }
#angle-demo kbd { font: 500 .65rem var(--mv-font-mono); padding: 0 .25rem; border-radius: 3px; background: var(--mv-bg-muted); border: 1px solid var(--mv-border); }
</style>
<div class="ad-stage"><div class="ad-shape" id="angle-demo-shape"></div></div>
<div class="ad-panel" role="group" aria-label="Inspector">
<p class="ad-head">Transform</p>
<div class="ad-row">
<span id="angle-demo-l1">Rotation</span>
<mv-angle-dial id="angle-demo-rot" name="rotation" value="-15" aria-labelledby="angle-demo-l1"></mv-angle-dial>
<mv-scrub-field id="angle-demo-rot-field" aria-label="Rotation in degrees" unit="°" value="345" min="0" max="360" wrap precision="0"></mv-scrub-field>
</div>
<p class="ad-head">Fill</p>
<div class="ad-row">
<span id="angle-demo-l2">Gradient</span>
<mv-angle-dial id="angle-demo-grad" class="ad-big" name="angle" value="135" snap="45" readout aria-labelledby="angle-demo-l2"></mv-angle-dial>
<span></span>
</div>
<p class="ad-hint">Drag the dial or use the arrow keys. <kbd>Shift</kbd> snaps to the ticks, <kbd>Alt</kbd> drags freely, double-click resets.</p>
</div>
</div>
<script type="module">
const $ = (id) => document.getElementById(`angle-demo-${id}`);
const apply = () => {
$("shape").style.rotate = `${$("rot").value}deg`;
$("shape").style.background = `linear-gradient(${$("grad").value}deg, oklch(0.8 0.13 190), oklch(0.6 0.2 265) 55%, oklch(0.68 0.2 340))`;
};
$("rot").addEventListener("mv-input", (e) => { $("rot-field").value = e.detail.value; apply(); });
$("rot-field").addEventListener("mv-input", (e) => { $("rot").value = e.detail.value; apply(); });
$("grad").addEventListener("mv-input", apply);
Promise.all(["mv-angle-dial", "mv-scrub-field"].map((t) => customElements.whenDefined(t))).then(() => requestAnimationFrame(apply));
</script>API
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
value | number | 0 | Angle in degrees, normalized to [0, 360). Restored on form reset and on double-click. |
name | string | Form field name; the form receives the number of degrees. | |
label | string | Angle | Accessible name. aria-labelledby / aria-label on the element or an external <label> work too. |
step | number | 1 | Arrow-key step (Alt: a tenth of it, when precision allows). |
snap | number | 15 | Snap grid: Shift while dragging or with the arrows, PageUp/PageDown step, and the spacing of the ticks. |
precision | number | 0 | Decimal places kept. |
readout | boolean | Shows the value in the center of the knob (for sizes of about 3rem and up). | |
locale | string | Number formatting locale (defaults to the page lang, then en-US). | |
disabled / readonly / required | boolean | Standard form states. |
Properties
| Name | Type | Description |
|---|---|---|
value | number | Current angle. |
valueAsNumber | number | Same as value. |
defaultValue | number | Value restored on form reset and double-click (the initial value). |
strings | Partial<Record<string, string>> | Overrides for every text: label ("Angle"), valueText ("{value} degrees"). English defaults. |
Methods
| Name | Description |
|---|---|
focus() | Focuses the slider. |
Events
| Name | Description |
|---|---|
mv-input | While dragging and on every key press. detail: { value }. |
mv-change | On release (if the value changed) and on every key press. detail: { value }. |
CSS classes
| Name | Description |
|---|---|
mv-angle-dial-face / -ticks / -tick / -arc / -plate / -needle / -knob / -readout / -badge | Generated parts. -tick[data-major] marks every 90°. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-angle-dial-size | var(--mv-control-sm) | Diameter; ticks, knob and readout scale with it. |
--mv-angle-dial-bg | Background of the tick band. |
Accessibility
The face has role="slider" with aria-valuemin 0, aria-valuemax 359 (the last value before it wraps, finer with precision), aria-valuenow and aria-valuetext “135 degrees” (localizable). Keyboard: →/↑ and ←/↓ by step (wrapping past 0°), Alt+arrow for a tenth of a step, Shift+arrow to the next snap tick, PageUp/PageDown ±snap, Home 0°, End the maximum. Esc while dragging restores the starting angle. Named by label, aria-labelledby/aria-label on the element, or a <label>. Visible focus ring; forced colors use system colors (Highlight for the value); the value bubble briefly shows after a key press; motion is limited to short transitions.