Code Block
components specs/components/code-block.kmd
Code-block widget — the multi-framework code-tabs surface that hosts per-format code samples behind a shared tab strip (HTML / Flutter / Compose / SwiftUI / Web Components), with a per-tab copy button and a transient "Copied" toast. The framework choice persists across the session so picking "Flutter" once shows Flutter in every code block.
When this spec applies
Primary triggers
- Embed a multi-framework code sample in KDS
All triggers
- Render a code sample on a Koder docs/design surface
- Show the same widget across multiple frameworks
- Add a copy-to-clipboard affordance to a code block
Specification body
Spec — Code Block
Facet Visual do Koder Design. The KDS realization of material.io's multi-framework code panels — the highest-value "designer crosses to developer" surface. Implemented in design-gen (
internal/kindsCodeTabs+assets/jstab switcher; shipped in tools/design-gen#044, copy button #028).
Anatomy
┌───────────────────────────────────────────────┐
│ [HTML] Flutter Compose SwiftUI Web [Copy] │ ← tab strip + copy
├───────────────────────────────────────────────┤
│ <button class="kd-btn filled">Save</button> │
│ …only the selected framework's panel shows… │ ← <pre><code>
└───────────────────────────────────────────────┘
- Tab strip: one tab per framework supplied; the active tab is
marked
aria-selected="true". Frameworks with no sample for a given block are omitted (no empty tabs). - Panels: one
<pre><code>per framework; only the selected one is visible (othershidden). Mono type role (--kds-font-mono). - Copy button: top-right; copies the visible panel's text.
R1 — Single logical widget, N syntaxes
A code-block tags N samples that express the same widget in different frameworks. Switching tabs swaps syntax only — never the widget being demonstrated. A block MAY carry a single framework (then the tab strip collapses to a label, not a control).
R2 — Shared, persisted framework selection
All code-blocks on a page (and across the session) share one framework
choice, persisted in localStorage["kds.framework"]. Picking a
framework in any block updates every other block on the page
immediately and restores on the next visit. Default when unset: the
first framework declared on the block (HTML where present).
R3 — Syntax-agnostic rendering
Panels render pre-escaped, server-highlighted code. No client-side
syntax-highlight dependency on the render path
(policies/headless-first.kmd — the headless build emits the same
HTML the user sees). Long lines scroll horizontally within the panel;
they do not wrap or expand the page.
R4 — Copy affordance + transient confirmation
Each block exposes a copy button that writes the visible panel's raw
text to the clipboard and shows a transient "Copied" toast for
~1.5 s, then reverts. The button keeps its label/icon between states
(no layout shift). On clipboard failure the toast reads the canonical
error per specs/errors/user-facing-messages.kmd — never a silent
no-op. Reuses the SDK copy primitive (policies/reuse-first.kmd).
R5 — Accessibility
- Tab strip is a
role="tablist"; tabs arerole="tab"witharia-controlspointing at their panel; panels arerole="tabpanel". - Arrow keys move between tabs; Enter/Space activates; the copy button
is reachable by Tab and announces its state change via
aria-live. - Color contrast of tab/active states meets the
themes/color-roles.kmdAA floor in both light and dark.
R6 — Degradation (no JS)
With JavaScript disabled, every framework panel is visible (stacked, each under its framework label) and the copy button is hidden — the content is never gated behind the switcher.
Out of scope
- Editable / runnable code (playground) — see the component prop playground surface, not this block.
- Diff rendering — a separate future surface.
Requirements (testable)
Requirement: Shared, persisted framework selection {#req-code-block-shared-framework}
All code-blocks on a page and across the session SHALL share one framework choice, persisted in localStorage["kds.framework"]; picking a framework in any block SHALL update every other block on the page immediately and SHALL restore the same choice on the next visit, defaulting to the first framework declared on the block when unset.
Scenario: Picking a framework updates every block on the page
- GIVEN a page with two code-blocks each exposing HTML and Flutter tabs, both showing HTML
- WHEN the user activates the Flutter tab in the first block
- THEN the first block's visible panel is the Flutter panel
- AND the second block's visible panel switches to its Flutter panel
- AND
localStorage["kds.framework"]equals"Flutter"
Scenario: Choice restores on next visit
- GIVEN
localStorage["kds.framework"]is set to"Flutter"before load - WHEN the page loads a code-block that declares HTML and Flutter
- THEN the Flutter tab is marked
aria-selected="true" - AND the Flutter panel is the visible panel
Scenario: Default when unset is the first declared framework
- GIVEN no
kds.frameworkkey exists in localStorage - WHEN a code-block declaring HTML then Flutter loads
- THEN the HTML tab is the active tab
- AND the HTML panel is the only visible panel
Requirement: Copy affordance with transient confirmation {#req-code-block-copy-toast}
Each code-block SHALL expose a copy button that writes the visible panel's raw text to the clipboard and shows a transient "Copied" toast for about 1.5 s before reverting, SHALL keep its label and icon between states with no layout shift, and on clipboard failure SHALL surface the canonical user-facing error rather than a silent no-op.
Scenario: Copy writes the visible panel and confirms
- GIVEN a code-block whose visible Flutter panel contains the text
Widget build() - WHEN the user clicks the copy button
- THEN the clipboard contents equal
Widget build() - AND a "Copied" toast appears
- AND the toast disappears after about 1.5 s while the button label and icon are unchanged
Scenario: Clipboard failure surfaces the canonical error
- GIVEN the clipboard write is denied by the environment
- WHEN the user clicks the copy button
- THEN the toast shows the canonical error message per
specs/errors/user-facing-messages.kmd - AND the action does not fail silently
Requirement: Syntax-agnostic horizontal scrolling {#req-code-block-no-wrap-scroll}
Panels SHALL render pre-escaped, server-highlighted code with no client-side syntax-highlight dependency on the render path, and long lines SHALL scroll horizontally within the panel without wrapping or expanding the page width.
Scenario: A long line scrolls within the panel
- GIVEN a code-block panel containing a line wider than the panel
- WHEN the panel is rendered
- THEN the line does not wrap onto a second visual row
- AND the panel provides horizontal scroll for the overflow
- AND the page body width is not expanded by the panel
Requirement: Accessible tablist and keyboard operation {#req-code-block-a11y-keyboard}
The tab strip SHALL be a role="tablist" whose tabs are role="tab" with aria-controls referencing their role="tabpanel", arrow keys SHALL move between tabs with Enter or Space activating the focused tab, the copy button SHALL be reachable by Tab and announce its state change via aria-live, and tab/active-state contrast SHALL meet the AA floor in both light and dark themes.
Scenario: Arrow keys move focus and Enter activates a tab
- GIVEN a code-block with the HTML tab focused and active
- WHEN the user presses ArrowRight and then Enter on the Flutter tab
- THEN the Flutter tab has
aria-selected="true" - AND the panel referenced by that tab's
aria-controlsbecomes visible - AND its
roleistabpanel
Scenario: Copy button is keyboard reachable and announces state
- GIVEN a code-block rendered with keyboard focus at the start of its controls
- WHEN the user tabs to the copy button and activates it
- THEN the button receives focus via Tab
- AND the state change is announced through an
aria-liveregion
Requirement: No-JS degradation shows all panels {#req-code-block-nojs-degradation}
With JavaScript disabled, every framework panel SHALL be visible, stacked and each under its framework label, and the copy button SHALL be hidden, so the code content is never gated behind the switcher.
Scenario: All panels are shown when JS is disabled
- GIVEN a code-block declaring HTML and Flutter rendered with JavaScript disabled
- WHEN the page is displayed
- THEN both the HTML and Flutter panels are visible and not
hidden - AND each panel is preceded by its framework label
- AND the copy button is not shown
References
specs/components/buttons.kmdspecs/interaction/states.kmdspecs/themes/color-roles.kmdspecs/fonts/typography.kmd