Segmented <mv-segmented>

フォームに対応し、ラジオグループのセマンティクスを持つセグメントコントロール:浮き上がったつまみがわずかにオーバーシュートしながら選択中の項目へ滑り、細い線が非選択の項目を区切ります。3 種類のサイズがあり、アイコンのみの項目にはツールチップで名前が付きます。

カテゴリーフォーム
タイプWeb Component (<mv-segmented>)
ステータス安定版
同時にインストールされるものtooltip
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

インストール

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

Marvelous UI の MCP サーバーを使う AI エージェント:install_components({ slugs: ["segmented"], target_dir: "<absolute path>/src/marvelous", framework: "react" })。

コピーされるファイル(依存関係を含む):tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/position.js, components/tooltip/tooltip.js, components/tooltip/tooltip.css, core/form.js, components/segmented/segmented.js, components/segmented/segmented.css。

使い方

クイックスタート、動作する最小限のマークアップ:

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

基本となるマークアップです。ここから属性、data-*、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>
    <mv-segmented label="Orders" value="open">
      <button value="all">All <span class="mv-segmented-count">42</span></button>
      <button value="open">Open <span class="mv-segmented-count">18</span></button>
      <button value="late">Late <span class="mv-segmented-count">3</span></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

NameタイプDescription
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

NameタイプDescription
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. A trailing <span class="mv-segmented-count"> shows a count ("Open 18") that is left out of the default value.

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.
mv-segmented-countCount pill after an option's label (<span class="mv-segmented-count">18</span>); not part of the default value.

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. A count is read with its option ("Open 18"): add an aria-label on the button when the number needs words ("Open, 18 orders").

このページは AI で翻訳されています。翻訳の問題を報告