beta
Angle Dial <mv-angle-dial>
Campo de ángulo en grados que se lee como en CSS (0° arriba, sentido horario): un botón giratorio dentro de un anillo de marcas que sirve también de cuadrícula de ajuste, con las marcas detrás del botón iluminadas y apagándose con la distancia. Arrastra con topes suaves cada 45° o con Shift para ajustar, teclado completo de slider, asociado a formularios.
| Categoría | Formularios |
|---|---|
| Tipo | Web Component (<mv-angle-dial>) |
| Estado | beta |
| 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
Instalación
node scripts/add.mjs angle-dial --out ./src/marvelousAgente de IA con el servidor MCP de Marvelous UI: install_components({ slugs: ["angle-dial"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Archivos copiados (dependencias incluidas): 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.
Uso
Marcado de referencia: parte de él y personalízalo con atributos, data-* y 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 | Tipo | 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 | Tipo | 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.