Services Grid
A three-column grid of services, each an icon, a title and a line of copy.
6 个框架初级
Category tabs switching service panels, with the full ARIA tabs keyboard pattern.
<!--
The real tabs pattern, not a row of buttons that hide divs. Three things make
it that: only the selected tab is in the tab sequence (roving tabindex), so
Tab lands on the strip once and the next Tab goes to the panel rather than
through every tab; arrows move selection inside the strip; and each panel is
wired to its tab both ways with aria-controls / aria-labelledby.
-->
<section class="svc-tabs" aria-labelledby="svc-tabs-title">
<h2 class="svc-tabs__title" id="svc-tabs-title">Find the engagement that fits</h2>
<div class="svc-tabs__strip" role="tablist" aria-label="Service categories">
<button class="svc-tabs__tab" type="button" role="tab" id="svc-tab-build" aria-controls="svc-panel-build" aria-selected="true" tabindex="0">Build</button>
<button class="svc-tabs__tab" type="button" role="tab" id="svc-tab-scale" aria-controls="svc-panel-scale" aria-selected="false" tabindex="-1">Scale</button>
<button class="svc-tabs__tab" type="button" role="tab" id="svc-tab-advise" aria-controls="svc-panel-advise" aria-selected="false" tabindex="-1">Advise</button>
</div>
<div class="svc-tabs__panel" role="tabpanel" id="svc-panel-build" aria-labelledby="svc-tab-build" tabindex="0">
<h3 class="svc-tabs__panel-title">Build</h3>
<p class="svc-tabs__panel-copy">A cross-functional squad that ships your first production release in twelve weeks.</p>
</div>
<div class="svc-tabs__panel" role="tabpanel" id="svc-panel-scale" aria-labelledby="svc-tab-scale" tabindex="0" hidden>
<h3 class="svc-tabs__panel-title">Scale</h3>
<p class="svc-tabs__panel-copy">Platform, performance and on-call practice for the traffic you are about to get.</p>
</div>
<div class="svc-tabs__panel" role="tabpanel" id="svc-panel-advise" aria-labelledby="svc-tab-advise" tabindex="0" hidden>
<h3 class="svc-tabs__panel-title">Advise</h3>
<p class="svc-tabs__panel-copy">Fractional architecture review for teams who need a second opinion, not a supplier.</p>
</div>
</section>
<script>
document.querySelectorAll('.svc-tabs').forEach(function (root) {
var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
function select(tab) {
tabs.forEach(function (item) {
var selected = item === tab;
item.setAttribute('aria-selected', String(selected));
// The roving part: unselected tabs leave the tab sequence entirely.
item.tabIndex = selected ? 0 : -1;
var panel = document.getElementById(item.getAttribute('aria-controls'));
if (panel) panel.hidden = !selected;
});
}
tabs.forEach(function (tab) {
tab.addEventListener('click', function () {
select(tab);
});
tab.addEventListener('keydown', function (event) {
var index = tabs.indexOf(tab);
var next = -1;
if (event.key === 'ArrowRight') next = (index + 1) % tabs.length;
else if (event.key === 'ArrowLeft') next = (index - 1 + tabs.length) % tabs.length;
else if (event.key === 'Home') next = 0;
else if (event.key === 'End') next = tabs.length - 1;
else return;
event.preventDefault();
select(tabs[next]);
tabs[next].focus();
});
});
});
</script>| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
kicker | string | - | 显示在标题上方的小标签。 |
title必填 | string | - | 卡片的标题文本。 |
categories必填 | ServiceCategory[] | - | 标签页,每个包含标签和对应面板内容。 |
ariaLabel | string | 'Service categories' | 按钮的无障碍名称。仅有图标的按钮必须设置。 |
className | string | - | 合并到根元素上的额外类名。 |
This is the real tabs pattern, not a row of buttons that hide divs. Three things make it that. The roving tabindex means only the selected tab is in the tab sequence, so Tab enters the strip once and the next Tab goes to the panel rather than through every category. Arrow keys move selection inside the strip, with Home and End jumping to the ends - without them the roving tabindex has locked every tab but one out of reach rather than tidied the tab order. And each panel is wired to its tab both ways with `aria-controls` and `aria-labelledby`. Selection is marked by a 2px rule as well as colour, because colour alone leaves the active tab unmarked for anyone who cannot separate the hues. The strip scrolls rather than wraps: two rows of tabs above one panel and it stops being obvious which is selected.