{# Tabs — roadmap 0.7, pulled forward. Basecoat already ships `.tabs` and its behaviour: its vendored script owns the selection, the `hidden` on the other panels, and the ArrowLeft/Right/Home/End keys. What `components/tabs.css` does not ship is a skin — that file is flex direction and `outline-none`, nothing else. The style pack does ship one, a pill strip; fjkit draws underline tabs, so `fjkit.css` turns the pack's geometry off before painting its own. Read that block before changing anything here: the two skins overlapping already put a phantom scrollbar on the card's edge once. This macro carries the aria wiring the script needs, and nothing else. The contract Basecoat's JS reads: .tabs > [role="tablist"] > [role="tab"][aria-controls=""] .tabs > [role="tabpanel"][id=""] Getting that pairing right by hand is what a component should remove. {% call tabs([{"id": "main", "label": "main.py"}, {"id": "router", "label": "router.py"}], label="File") %} {% call tab_panel("main") %}…{% endcall %} {% call tab_panel("router") %}…{% endcall %} {% endcall %} `items` is a list of dicts, the shape `table` takes its columns in, and extra keys are ignored — so a caller already holding a list of files passes it straight through instead of building a parallel one. It is the light half, so it is a parameter while the panels are the body: the same trade as `card`'s `actions`, because a two-slot call block renders its body once per slot and the body here is every panel's content. Ids are page-unique, like any id. A page with two tab groups over the same subject prefixes them ("file-main", "diff-main"), as it would for any other pair of elements. #} {% from "ui/attrs.html" import attrs %} {# tabs(items, label, selected, orientation, variant) orientation: horizontal | vertical — the value Basecoat's key handler reads to decide whether Left/Right or Up/Down moves between tabs, so it is a closed lookup rather than interpolated: `aria-orientation="sideways"` would leave the arrow keys dead with no other symptom. variant: underline | segmented — a shape, not a colour role like the `variant` on `button` or `badge`. `underline` is fjkit's strip and the default. `segmented` is the style pack's own pill strip, which `fjkit.css` leaves unpainted: a tab group nested inside a tab panel then reads as a mode switch rather than as a second level of navigation. An unknown value falls back to `underline`. `data-variant` goes on the tablist, not the container, because that is where every pack's selector looks, and only `segmented` writes it. Nesting is safe with Basecoat's script: it takes the container's first `[role="tablist"]` in document order — this `