Underline Tabs
A keyboard-accessible tab strip with the active tab underlined.
6 個のフレームワーク中級
A tab strip that scrolls sideways when the tabs outrun the row.
<!-- The tablist is overflow-x-auto with scroll-snap, so it scrolls sideways instead of overflowing the page at 320px. -->
<div class="max-w-2xl" data-tabs-scrollable>
<div class="flex snap-x gap-6 overflow-x-auto border-b border-gray-200 dark:border-gray-800" role="tablist" aria-label="Project sections">
<button class="relative shrink-0 snap-start whitespace-nowrap px-0.5 py-3 text-sm font-medium text-gray-600 transition-colors after:absolute after:inset-x-0 after:-bottom-px after:h-0.5 after:bg-transparent hover:text-gray-900 focus-visible:rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 motion-reduce:transition-none aria-selected:text-blue-600 aria-selected:after:bg-blue-600 dark:text-gray-400 dark:hover:text-gray-100 dark:focus-visible:ring-blue-400 dark:aria-selected:text-blue-400 dark:aria-selected:after:bg-blue-400" type="button" role="tab" id="scr-overview" aria-controls="scr-panel-overview" aria-selected="true" tabindex="0">Overview</button>
<button class="relative shrink-0 snap-start whitespace-nowrap px-0.5 py-3 text-sm font-medium text-gray-600 transition-colors after:absolute after:inset-x-0 after:-bottom-px after:h-0.5 after:bg-transparent hover:text-gray-900 focus-visible:rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 motion-reduce:transition-none aria-selected:text-blue-600 aria-selected:after:bg-blue-600 dark:text-gray-400 dark:hover:text-gray-100 dark:focus-visible:ring-blue-400 dark:aria-selected:text-blue-400 dark:aria-selected:after:bg-blue-400" type="button" role="tab" id="scr-analytics" aria-controls="scr-panel-analytics" aria-selected="false" tabindex="-1">Analytics</button>
<button class="relative shrink-0 snap-start whitespace-nowrap px-0.5 py-3 text-sm font-medium text-gray-600 transition-colors after:absolute after:inset-x-0 after:-bottom-px after:h-0.5 after:bg-transparent hover:text-gray-900 focus-visible:rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 motion-reduce:transition-none aria-selected:text-blue-600 aria-selected:after:bg-blue-600 dark:text-gray-400 dark:hover:text-gray-100 dark:focus-visible:ring-blue-400 dark:aria-selected:text-blue-400 dark:aria-selected:after:bg-blue-400" type="button" role="tab" id="scr-members" aria-controls="scr-panel-members" aria-selected="false" tabindex="-1">Members</button>
<button class="relative shrink-0 snap-start whitespace-nowrap px-0.5 py-3 text-sm font-medium text-gray-600 transition-colors after:absolute after:inset-x-0 after:-bottom-px after:h-0.5 after:bg-transparent hover:text-gray-900 focus-visible:rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 motion-reduce:transition-none aria-selected:text-blue-600 aria-selected:after:bg-blue-600 dark:text-gray-400 dark:hover:text-gray-100 dark:focus-visible:ring-blue-400 dark:aria-selected:text-blue-400 dark:aria-selected:after:bg-blue-400" type="button" role="tab" id="scr-billing" aria-controls="scr-panel-billing" aria-selected="false" tabindex="-1">Billing</button>
</div>
<div class="pt-4 text-sm leading-relaxed text-gray-600 focus-visible:rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 dark:text-gray-300 dark:focus-visible:ring-blue-400" role="tabpanel" id="scr-panel-overview" aria-labelledby="scr-overview" tabindex="0"><p>A summary of the whole project at a glance.</p></div>
<div class="pt-4 text-sm leading-relaxed text-gray-600 focus-visible:rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 dark:text-gray-300 dark:focus-visible:ring-blue-400" role="tabpanel" id="scr-panel-analytics" aria-labelledby="scr-analytics" tabindex="0" hidden><p>Traffic climbed 8% week over week.</p></div>
<div class="pt-4 text-sm leading-relaxed text-gray-600 focus-visible:rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 dark:text-gray-300 dark:focus-visible:ring-blue-400" role="tabpanel" id="scr-panel-members" aria-labelledby="scr-members" tabindex="0" hidden><p>24 members across 5 teams.</p></div>
<div class="pt-4 text-sm leading-relaxed text-gray-600 focus-visible:rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 dark:text-gray-300 dark:focus-visible:ring-blue-400" role="tabpanel" id="scr-panel-billing" aria-labelledby="scr-billing" tabindex="0" hidden><p>Team plan, renews on the 1st.</p></div>
</div>
<script>
(function () {
document.querySelectorAll('[data-tabs-scrollable]').forEach(function (root) {
var tabs = [].slice.call(root.querySelectorAll('[role="tab"]'));
function panelFor(t) { return document.getElementById(t.getAttribute('aria-controls')); }
function select(i, focus) {
tabs.forEach(function (t, n) {
var on = n === i;
t.setAttribute('aria-selected', String(on));
t.tabIndex = on ? 0 : -1;
var p = panelFor(t);
if (p) p.hidden = !on;
});
if (focus) tabs[i].focus(); // focusing scrolls the tab into view
}
tabs.forEach(function (t, i) {
t.addEventListener('click', function () { select(i, false); });
t.addEventListener('keydown', function (e) {
var n = -1;
if (e.key === 'ArrowRight') n = (i + 1) % tabs.length;
else if (e.key === 'ArrowLeft') n = (i - 1 + tabs.length) % tabs.length;
else if (e.key === 'Home') n = 0;
else if (e.key === 'End') n = tabs.length - 1;
else return;
e.preventDefault();
select(n, true);
});
});
});
})();
</script>| Prop | 型 | デフォルト | 説明 |
|---|---|---|---|
items必須 | TabItem[] | - | レンダリングする項目の配列。 |
defaultTabId | string | - | マウント時に展開する項目の ID。 |
onSelect | (id: string) => void | - | ユーザーが選択したメニュー項目を引数に呼ばれます。 |
className | string | - | ルート要素にマージされる追加クラス。 |
The strip is `overflow-x-auto` with scroll-snap, so it never pushes the page wider than the viewport at 320px; arrow-key focus scrolls the next tab into view for free. Swap the underline paint for pills without touching the scroll behaviour - it lives on the tablist, not the tabs.