bêta

Segmented <mv-segmented>

Contrôle segmenté avec la sémantique d’un groupe de boutons radio, associé aux formulaires : une poignée en relief glisse vers l’option sélectionnée avec un léger dépassement, de fins filets séparent les options inactives, trois tailles, et des options à icône seule nommées par des infobulles.

CatégorieFormulaires
TypeWeb Component (<mv-segmented>)
Statutbêta
Installe aussitooltip
Keywordssegmented, radio-group, toggle, inspector, sliding-indicator, tooltip, form-associated, editor, sizes

When to use

  • An inspector offers two to five mutually exclusive options, like text alignment or view mode
  • Icon-only options need a compact radio group with tooltips for their names
  • A form should submit one choice from a short set while keeping it visible at a glance

Avoid when

  • The options switch between panels of content → use Tabs instead
  • There are many options or long labels that would not fit on one row → use Select instead
  • Several options can be on at once → use Toggle instead

Installation

node scripts/add.mjs segmented --out ./src/marvelous

Agent IA avec le serveur MCP de Marvelous UI : install_components({ slugs: ["segmented"], 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, core/position.js, components/segmented/segmented.js, components/segmented/segmented.css, components/tooltip/tooltip.js, components/tooltip/tooltip.css.

Utilisation

Démarrage rapide, le balisage minimal qui fonctionne :

<mv-segmented label="View" value="list">
  <button value="list">List</button>
  <button value="grid">Grid</button>
</mv-segmented>

Balisage de référence : partez de celui-ci et personnalisez-le avec les attributs, data-* et les variables CSS :

<div id="seg-demo">
  <style>
    #seg-demo { display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: center; justify-content: center; width: 100%; }
    #seg-demo .sgd-stage { display: grid; place-items: center; width: 17rem; height: 16.5rem; padding: 1.25rem; border-radius: var(--mv-radius-xl); background: var(--mv-bg); border: 1px solid var(--mv-border); }
    #seg-demo .sgd-card { width: 100%; padding: 1rem 1.1rem; border-radius: var(--mv-radius-lg); background: var(--mv-surface-raised); border: 1px solid var(--mv-border); transition: box-shadow var(--mv-duration-normal) var(--mv-ease-out); }
    #seg-demo .sgd-card h4 { margin: 0 0 .35rem; font: 650 1rem/1.25 var(--mv-font-sans); }
    #seg-demo .sgd-card p { margin: 0; font-size: .8rem; line-height: 1.5; color: var(--mv-fg-muted); }
    #seg-demo .sgd-card[data-density="compact"] { padding: .6rem .75rem; }
    #seg-demo .sgd-card[data-density="roomy"] { padding: 1.5rem 1.4rem; }
    #seg-demo .sgd-panel { display: grid; gap: .6rem; width: 17.5rem; padding: .8rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-lg); background: var(--mv-surface); box-shadow: var(--mv-shadow-sm); }
    #seg-demo .sgd-head { margin: 0 0 .1rem; font: 600 .7rem var(--mv-font-sans); letter-spacing: .06em; text-transform: uppercase; color: var(--mv-fg-subtle); }
    #seg-demo .sgd-row { display: grid; grid-template-columns: 4.25rem 1fr; align-items: center; gap: .5rem; font-size: .75rem; color: var(--mv-fg-muted); }
    #seg-demo .sgd-sizes { display: flex; flex-wrap: wrap; gap: .75rem; align-items: center; justify-content: center; width: 100%; }
  </style>
  <div class="sgd-stage">
    <div class="sgd-card" id="seg-demo-card">
      <h4>New collection</h4>
      <p>Soft fabrics and clean cuts, made to last well beyond one season.</p>
    </div>
  </div>
  <div class="sgd-panel" role="group" aria-label="Inspector">
    <p class="sgd-head">Text</p>
    <div class="sgd-row"><span id="seg-demo-l1">Alignment</span>
      <mv-segmented id="seg-demo-align" icons full aria-labelledby="seg-demo-l1" name="align" value="left">
        <button value="left" aria-label="Align left"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 6h16M4 12h10M4 18h13"/></svg></button>
        <button value="center" aria-label="Align center"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 6h16M7 12h10M5.5 18h13"/></svg></button>
        <button value="right" aria-label="Align right"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 6h16M10 12h10M7 18h13"/></svg></button>
        <button value="justify" aria-label="Justify"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 6h16M4 12h16M4 18h16"/></svg></button>
      </mv-segmented>
    </div>
    <div class="sgd-row"><span id="seg-demo-l2">Case</span>
      <mv-segmented id="seg-demo-case" full aria-labelledby="seg-demo-l2" name="case" value="none">
        <button value="none">Aa</button>
        <button value="uppercase">AA</button>
        <button value="lowercase">aa</button>
        <button value="small-caps" disabled>Sᴄ</button>
      </mv-segmented>
    </div>
    <p class="sgd-head" style="margin-top:.35rem">Card</p>
    <div class="sgd-row"><span id="seg-demo-l3">Shadow</span>
      <mv-segmented id="seg-demo-shadow" full aria-labelledby="seg-demo-l3" name="shadow" value="sm">
        <button value="none">None</button>
        <button value="sm">Soft</button>
        <button value="xl">Strong</button>
      </mv-segmented>
    </div>
    <div class="sgd-row"><span id="seg-demo-l4">Density</span>
      <mv-segmented id="seg-demo-density" full data-size="sm" aria-labelledby="seg-demo-l4" name="density" value="default">
        <button value="compact">Compact</button>
        <button value="default">Default</button>
        <button value="roomy">Roomy</button>
      </mv-segmented>
    </div>
  </div>
  <div class="sgd-sizes">
    <mv-segmented data-size="lg" label="Billing period" value="yearly">
      <button value="monthly">Monthly</button>
      <button value="yearly">Yearly · save 20%</button>
    </mv-segmented>
  </div>
</div>
<script type="module">
  const root = document.getElementById("seg-demo");
  const card = document.getElementById("seg-demo-card");
  const get = (id) => document.getElementById(`seg-demo-${id}`).value;
  const apply = () => {
    card.style.textAlign = get("align");
    card.style.textTransform = get("case");
    const s = get("shadow");
    card.style.boxShadow = s === "none" ? "none" : `var(--mv-shadow-${s})`;
    card.dataset.density = get("density");
  };
  root.addEventListener("mv-change", apply);
  customElements.whenDefined("mv-segmented").then(() => requestAnimationFrame(apply));
</script>

API

Attributes

NameTypeDescription
valuestringValue of the selected option (otherwise the first enabled one). Restored on reset.
namestringForm field name.
labelstringAccessible name of the group (or aria-label / aria-labelledby).
iconsbooleanIcon-only options: each button’s aria-label becomes a tooltip (<mv-tooltip>).
fullbooleanFull width, equal-width segments.
data-sizesm | lgSmaller or larger track (default between the two).
disabled / readonlybooleanDisables the whole group (also via <fieldset disabled>) or freezes it while keeping it focusable.

Properties

NameTypeDescription
valuestringCurrent value; setting it selects the matching option without firing events.
itemsHTMLButtonElement[]Option buttons. Buttons added or removed later are picked up.
selectedItemHTMLButtonElement | nullSelected option.

Methods

NameDescription
focus()Focuses the selected option (or the first enabled one).

Events

NameDescription
mv-input / mv-changeNew option selected (click or arrow keys). detail: { value, item }.

Content structure

NameDescription
<button value>One option per child button (text, icon or both); disabled for an unavailable option.

CSS classes

NameDescription
mv-segmented-item / -indicatorOptions and gliding thumb. data-selected marks the chosen option, data-divider an idle option that shows a hairline.

CSS variables

NameDefaultDescription
--mv-segmented-heightvar(--mv-control-sm)Height (overrides data-size).
--mv-segmented-radiusvar(--mv-radius-md)Outer radius; the segments use this radius minus the inner padding.
--mv-segmented-padding2pxGap between the track and the thumb.
--mv-segmented-bg / --mv-segmented-indicatorTrack and thumb background.
--mv-segmented-selected-fg / --mv-segmented-dividerSelected label color and hairline color.
--mv-segmented-easecubic-bezier(0.32, 1.25, 0.6, 1)Easing of the glide.

Accessibility

Named role="radiogroup" (aria-disabled, aria-readonly), options are role="radio" with aria-checked. A single tab stop (roving tabindex); arrow keys move and select, wrapping and mirrored in right-to-left layouts, Home/End jump to the ends and disabled options are skipped; a read-only group only moves focus. In the icon variant the name comes from the aria-label and the tooltip, also shown on keyboard focus, is not announced twice. The glide follows reduced motion, hover tints only apply to fine pointers, and forced colors draw the thumb in Highlight.

Cette page a été traduite par IA. Signaler un problème de traduction