Tooltip Group <mv-tooltip-group>

툴바처럼 여러 트리거가 공유하는 툴팁: 첫 지연 이후에는 하나의 말풍선이 버튼에서 버튼으로 미끄러지며 크기를 바꾸고 라벨을 크로스페이드하므로, 닫고 다시 열거나 두 번 기다릴 필요가 없습니다.

카테고리오버레이
유형Web Component (<mv-tooltip-group>)
상태안정
Keywordstooltip, morphic-tooltip, shared-layout, toolbar, hint, glide, morph

When to use

  • A toolbar of icon buttons needs one tooltip that slides between triggers instead of reopening each time
  • Users scan adjacent controls quickly and each label should appear instantly after the first delay
  • A label changes in place after an action, such as Copy link becoming Link copied

Avoid when

  • The triggers are scattered across the page rather than grouped side by side → use Tooltip instead
  • The hint holds interactive content or must work on touch devices → use Popover instead

설치

node scripts/add.mjs tooltip-group --out ./src/marvelous

Marvelous UI MCP 서버를 사용하는 AI 에이전트: install_components({ slugs: ["tooltip-group"], 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-group/tooltip-group.js, components/tooltip-group/tooltip-group.css.

사용법

기본 마크업입니다. 여기서 시작해 속성, data-*, CSS 변수로 커스터마이즈하세요:

<div id="tg-demo" style="display:flex;flex-wrap:wrap;gap:3rem 4rem;align-items:center;justify-content:center;padding-block:2.5rem">
  <style>
    #tg-demo .tg-bar { display: flex; align-items: center; gap: 2px; padding: 4px; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-lg); background: var(--mv-surface-raised); box-shadow: var(--mv-shadow-sm); }
    #tg-demo .tg-bar[aria-orientation="vertical"] { flex-direction: column; }
    #tg-demo .tg-sep { flex: none; width: 1px; height: 1.25rem; margin: 0 4px; background: var(--mv-border); }
    #tg-demo .tg-bar[aria-orientation="vertical"] .tg-sep { width: 1.25rem; height: 1px; margin: 4px 0; }
    #tg-demo .tg-bar svg { width: 1rem; height: 1rem; }
    #tg-demo .tg-cap { margin: .875rem 0 0; text-align: center; font-size: .75rem; color: var(--mv-fg-muted); }
  </style>

  <figure style="margin:0">
    <mv-tooltip-group delay="400">
      <div class="tg-bar" role="group" aria-label="Text formatting">
        <button class="mv-button" data-variant="ghost" data-size="icon" aria-label="Bold" data-tooltip="Bold" data-tooltip-kbd="⌘B"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 5h6a3.5 3.5 0 0 1 0 7H7zM7 12h7a3.5 3.5 0 0 1 0 7H7z"/></svg></button>
        <button class="mv-button" data-variant="ghost" data-size="icon" aria-label="Italic" data-tooltip="Italic" data-tooltip-kbd="⌘I"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M14 5h5M5 19h5M15 5 9 19"/></svg></button>
        <button class="mv-button" data-variant="ghost" data-size="icon" aria-label="Strikethrough" data-tooltip="Strikethrough" data-tooltip-kbd="⇧⌘X"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 12h16M16 6.5A4 3 0 0 0 12 5c-2.5 0-4 1.3-4 3M8 17.5A4 3 0 0 0 12 19c2.5 0 4-1.3 4-3"/></svg></button>
        <span class="tg-sep" aria-hidden="true"></span>
        <button class="mv-button" data-variant="ghost" data-size="icon" aria-label="Mention" data-tooltip-template="tg-demo-mention"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 5 0v-1a9 9 0 1 0-3.5 7.1"/></svg></button>
        <button class="mv-button" data-variant="ghost" data-size="icon" aria-label="Copy link" data-tooltip="Copy link" id="tg-demo-copy"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1"/></svg></button>
      </div>
    </mv-tooltip-group>
    <template id="tg-demo-mention"><span style="display:grid;gap:2px"><strong>Mention someone</strong><span style="opacity:.72;font-weight:400">Type @ then a name, like @Aiko</span></span></template>
    <figcaption class="tg-cap">One bubble slides from button to button</figcaption>
  </figure>

  <figure style="margin:0;display:flex;align-items:center;gap:1rem">
    <mv-tooltip-group placement="right" delay="300">
      <nav class="tg-bar" aria-label="Workspace">
        <button class="mv-button" data-variant="ghost" data-size="icon" aria-label="Inbox" data-tooltip="Inbox: 3 unread"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 13h4l1.5 3h5L16 13h4M5 6h14l1 7v5H4v-5z"/></svg></button>
        <button class="mv-button" data-variant="ghost" data-size="icon" aria-label="Calendar" data-tooltip="Calendar"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><rect x="4" y="5" width="16" height="15" rx="2"/><path d="M4 10h16M9 3v4M15 3v4"/></svg></button>
        <span class="tg-sep" aria-hidden="true"></span>
        <button class="mv-button" data-variant="ghost" data-size="icon" aria-label="Settings" data-tooltip="Settings" data-tooltip-kbd="⌘,"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M12 3v2.5M12 18.5V21M3 12h2.5M18.5 12H21M5.6 5.6l1.8 1.8M16.6 16.6l1.8 1.8M5.6 18.4l1.8-1.8M16.6 7.4l1.8-1.8"/></svg></button>
      </nav>
    </mv-tooltip-group>
    <figcaption class="tg-cap" style="margin:0;max-width:9rem;text-align:start">Side rail: placement="right", resizing to each label</figcaption>
  </figure>

  <script type="module">
    const copy = document.querySelector("#tg-demo-copy");
    let timer = 0;
    copy?.addEventListener("click", () => {
      navigator.clipboard?.writeText("https://example.com/doc/roadmap-2027").catch(() => {});
      copy.dataset.tooltip = "Link copied";
      copy.closest("mv-tooltip-group")?.show(copy); // the press closed it: confirm in place
      clearTimeout(timer);
      timer = setTimeout(() => { copy.dataset.tooltip = "Copy link"; }, 1600);
    });
  </script>
</div>

API

Attributes

Name유형DefaultDescription
placementtop | bottom | left | right[-start|-end]topDefault side (auto flip); override per trigger with data-placement.
delaynumber500Delay before the first appearance (ms). While the bubble is open, or just after any tooltip closed (mv-tooltip included), moving to another trigger is instant.
close-delaynumber160Grace period before closing when the pointer leaves a trigger (lets it cross the gap between two buttons).
offsetnumber8Distance from the trigger (px).
disabledbooleanDisables the whole group.
data-arrow="false"stringHides the arrow.

Properties

Name유형Description
activeTriggerHTMLElement | nullTrigger currently being described.
isOpenbooleanWhether the bubble is visible (read-only).

Methods

NameDescription
show(trigger)Shows the bubble on a trigger, sliding over if it is already open.
hide()Closes the surface.

Events

NameDescription
mv-openFirst appearance. detail: { trigger }.
mv-changeThe bubble slid to another trigger. detail: { trigger }.
mv-closeAfter closing.

Content structure

NameDescription
[data-tooltip]Any descendant carrying this text becomes a trigger (delegated: elements added later work too). Changing it while open crossfades the label in place.
[data-tooltip-kbd]Shortcut shown to the right of the label.
[data-tooltip-template="id"]Rich content: a clone of a <template id> in the document.
[data-placement]Trigger-specific side.
[data-tooltip-disabled]Ignores this trigger.

CSS variables

NameDefaultDescription
--mv-tooltip-bgBackground (shared with mv-tooltip).
--mv-tooltip-fgText color (shared with mv-tooltip).
--mv-tooltip-group-durationvar(--mv-duration-normal)Duration of the slide and resize between triggers.

Accessibility

A single role=tooltip bubble, linked to the active trigger with aria-describedby (skipped when the label only repeats the trigger name). Opens at once on keyboard focus (:focus-visible), closes on Escape, on press and when focus or pointer leaves the group. The open bubble can be hovered without closing (WCAG 1.4.13). Never opens on touch. Outgoing labels are aria-hidden. Reduced motion makes the slide and crossfade instant; forced colors add an outline.

이 페이지는 AI로 번역되었습니다. 번역 문제 신고