Segmented <mv-segmented>

라디오 그룹 시맨틱을 갖추고 폼과 연동되는 세그먼트 컨트롤: 떠 있는 썸이 살짝 지나쳤다 돌아오며 선택한 옵션으로 미끄러지고, 가는 선이 비활성 옵션을 구분하며, 세 가지 크기와 툴팁으로 이름을 붙이는 아이콘 전용 옵션을 제공합니다.

카테고리폼
유형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로 번역되었습니다. 번역 문제 신고