Polish Codeblock
The code block becomes one sleek card: bar, chip, copy button, field.
The clipboard path is sound and is kept. Verified against built output
(hugo build -D of a probe page holding every fence form): the text
copy-codeblock.ts writes is byte-identical to the fenced source for plain
blocks, linenos=true, linenos=table, hl_lines, tab-indented,
trailing-space, and file= blocks, and the line-number column is never
included, because Chroma carries each line’s newline inside its .cl span. The
styling around that path is what fails.
Current State
-
render-codeblock.htmland_partials/codeblock.htmlhold the same markup twice; the render hook never calls the partial, so every change is written twice and drifts. - The shell carries
container, so it inherits Tailwind’s container max-widths and the unlayered.containerrule inlayout.css(border: 1px solid var(--border),padding: min(2em, 10%),border-radius: 1em,margin-bottom: 20px, which beatsmb-8). A code block is therefore a framed card with 10 % padding inside a frame, capped at 40rem bysm:w-120 md:w-160and never filling the column. - Colours are literal Tailwind greys, not the token layer:
bg-gray-600bar,bg-gray-300 dark:bg-slate-700file band,border-gray-200 dark:border-gray-800,fill="#FFFFFF"icon.bg-gray-600(#4b5563) is the same in both schemes, and theCopylabel inherits the page foreground: about 1.9:1 on the grey bar in the light scheme, so the label is close to invisible there. - The bar is
flex h-10 w-full justify-betweenholding a 1.5rem icon at the left, the label in the middle, and a deadh-7 w-fulldiv on the right. The whole 10rem-tall row is.copy-button, so any click in it copies, and theidscodeblock-NNN/codeblock-button-NNNare dead weight: the script selects.codeand.copy-buttonby class. - The control is a
div, not abutton: no tab stop, no accessible name, no Enter/Space, no focus ring.eleButton.innerText = 'Copied!'overwrites the<p>, so the icon never changes, and each click stacks a fresh 2 second timer that can revert the label early. -
details="true"is broken: both templates emit<details><summary>…</summary></details>and then the block beside it, so nothing collapses and the print control’s “open every closeddetails” pass insite-header.tshas nothing to open. -
.highlightsetsoverflow: scroll, painting both scrollbars permanently, andfont-size: 1rem;--code-font-familyis read bysyntax-highlight.cssand defined nowhere, so the family is inherited by accident. - Syntax palette audit against the
#282c34field (WCAG 2.2 AA needs 4.5:1):.nt1.64,.go1.47,.ln/.lnt3.50, the.err/.kc/.ne/.nl/.gd/.gr/.gs/.gugroup 3.52, the.na/.nv/.o/.ow/.kn/.kpgroup 4.29. The last two groups arevulcancolours that were tuned for a light field.
Target Design
One card, overflow: hidden and border-radius: var(--radius), full column
width, hairline border, --shadow-sm, 1.5rem above and 2rem below:
- Bar, 44px,
--code-barsurface, hairline bottom edge: file name (mono, 0.875rem, ellipsised with the full path intitle), a language chip (uppercase, 0.6875rem,letter-spacing: 0.05em,--radius-sm, the same overline chip as.brand-markand.footer-copy), and the copy button at the right,margin-left: auto. - Chip text is the extension of
file, else the fence language, mapped to a display name (cpp→ C++,bash→ Bash,dosbatch→ Batch,xml→ HTML,gotmpl→ Go Template,md→ Markdown,text→ Text); with no file and no language it reads Text. - Copy button, a real
<button type="button">: 32px tall, icon plus label,--radius-sm,--code-hoveron hover, 180ms ease-out colour transition,:focus-visiblering 2px--code-focusat 2px offset,aria-label="Copy <file> code to clipboard". Two inline SVGs (copy, check) and a.is-copiedclass swap icon and label, while a visually-hiddenrole="status"region in the bar announces “Code copied to clipboard”, and.is-failedcarries “Copy failed”. Below 480px the label is dropped and the button is icon-only, at least 44px square on coarse pointers. - Field,
--code-bg(#282c34, kept in both schemes: thevulcanpalette is built for a dark field, and a dark code card in a light page is the convention GitHub and Vercel set):overflow-x: autoonly, 0.75rem/1rem padding,font-size: 0.875rem,line-height: 1.6,tab-size: 4, gutter numbers right-aligned with a hairline rule anduser-select: none. - Colours come from tokens added to
tokens.css(--code-bg,--code-bar,--code-bar-text,--code-bar-muted,--code-chip,--code-chip-text,--code-hover,--code-focus,--code-border,--code-gutter,--code-font-family: var(--font-mono)), each declared light first, dark in.dark, and paired throughlight-dark()in the@supportsblock, with the print andforced-colorsblocks of the component stylesheet flattening them. - Contrast fixes in
syntax-highlight.css, all measured on#282c34:.nt#7ee0a8(8.7:1),.go/.ln/.lnt#9aa3ad(5.5:1), the red group#ef8a97(5.8:1), the purple group#cf96d6(6.0:1). Bar text#dfe3e6(9.7:1), chip text#cfd6dd(8.6:1), muted label#aab2bb(5.9:1), focus ring#48c9b0(6.1:1). - Details collapses the whole card:
<details>wraps bar and field, with a--surface-2summary row carrying a chevron that rotates 90° when open,list-style: nonemarkers, and thesummarytext as supplied. - Scripts off hides the copy button, as
html:not([data-theme]) #theme-switchhides the scheme switch. - Print keeps the card:
.codeblock,.highlight,.code,.chroma,.copy-buttonandbuttonare the hooksprint.cssalready uses, so the bar prints white with#9a9a9arules, the button disappears, and the collapsed blocks reach the paper oncedetailsworks. Noh1-h6element goes in the bar, leaving the one-h1-per-page check untouched.
Work
- Rewrite
_partials/codeblock.htmlto the markup above, and reduce_markup/render-codeblock.htmlto attribute parsing plus a partial call. Keep the attribute contract intact:file,copy(default true),details,open,summary,trim. - Drop the dead
codeblock-NNN/codeblock-button-NNNids from the templates and theprint-codeshortcode; the script selects by class, and the shortcode’s filename-derived ids collide when one data file is printed twice. - Fix
detailsto wrap the card in both templates, and style.codeblock-detailsand.codeblock-summary. - Add
be-best/assets/css/codeblock.css(the pattern oftable.css,footer.css): component classes.codeblock,.codeblock-bar,.codeblock-name,.codeblock-lang,.codeblock-copy,.codeblock-details,.codeblock-summary, the reduced-motion andforced-colorsblocks, imported bymain.cssahead ofprint.css. - Add the
--code-*tokens totokens.css, keeping the four resolution paths that file already documents (light,.dark,light-dark(), print, forced colours), and map--font-codein@theme inline. - Rewrite the copy control in
copy-codeblock.ts: bind to the real button, guard a missingnavigator.clipboardwith a hidden-textarea fallback for insecure origins, one timer per button cleared on re-click, the.is-copiedclass and the announced result, and a reported failure state instead of a console line. The text extraction is kept, and widened: the numbering column is skipped wherever it sits (.ln,.lnt,.linenos,.ln_table,.lnt_table, so inline numbering is excluded as table numbering always was), and a code element holding bare text, which is what Chroma emits for a language it has no lexer for, is copied whole instead of empty. - Move the surface, scrollbar, size and family rules out of
.highlightinsyntax-highlight.cssintocodeblock.css, define--code-font-familyfor real, and land the contrast colours listed above. - Highlight markdown fences: map
mdto Chroma’smarkdownlexer instead oftext, and keeptext/plaintext unhighlighted. - Update
print.cssfor the bar (white surface,#9a9a9arules, button removed) in both the@media printblock and thehtml.print-modepreview, and drop its now-false comment about a fixed 160 column width. - Re-state the presentation in the specifications: the codeblock sentence in
content/specification/functional/index.md, and the render-hook, print-chrome and width passages incontent/specification/technical/index.md.
A finding, not a task: {hl_lines=[2,3]} in a fence’s attributes produces no
.hl element in Hugo 0.166, so the band styles apply to highlighted lines only
when hl_lines is set in the site or page configuration. The fence attribute
changes nothing in the built markup, and the copy text of such a block is the
source, as the criterion requires.
Acceptance Criteria
The checks below are run against built output and against be-best/; the
clipboard text is checked by replaying the script’s extraction over the built
markup of a probe page holding every fence form. The criteria that need a
browsing context - real clicks, the clipboard in three browsers, the widths, the
paper, and the page without scripts - were checked by hand on the served site.
- The literal greys, the fixed widths and the dead ids are gone:
grep -rE 'bg-gray-600|bg-gray-300|sm:w-120|md:w-160' be-best/layouts/prints nothing, and so does the same forcodeblock-button-. - The markup exists once, in the partial:
grep -c 'class="codeblock"' be-best/layouts/_markup/is 0. - In built output the card runs bar, file name, language chip, copy button,
in that order; the bar is
.codeblock-bar, the button is a real<button type="button">with anaria-labeland the.copy-buttonhook, and noh1-h6element sits inside the card. - Clicking, Enter and Space on the button all copy; the icon and label change to the copied state, are announced, and revert once, about 2s later, whatever the click pattern. The bar outside the button does not copy.
- On the probe page the clipboard text equals the fenced source exactly for
the plain,
file=,linenos=true,linenos=table,hl_lines, tab-indented, trailing-space andtextcases in Chrome, Firefox and Safari, with no line numbers, andhttp://origins copy through the fallback. - Every fenced block fills the content column at 320, 768, 1280 and 1920px; long lines scroll in the field only, with no vertical scrollbar and no inner frame or 10 % padding.
- In both schemes the bar text, chip, label and focus ring clear 4.5:1 (bar
text 9.7:1, chip 6.3:1, label 9.7:1, focus ring 6.1:1, the answer colours
7.8:1 copied and 5.2:1 failed, all of them 5.4:1 or better on the hover
and pressed surfaces) and every
.chromatoken class used by the corpus (cpp, bash, xml, yaml, dosbatch, json, basic, text) clears 4.5:1 on the field. The 21 token classes the corpus produces measure 5.0:1 to 8.7:1; the line numbers, which are decoration and are never selected, sit at 4.0:1 on thehl_linesband. -
details="true"renders the card inside<details>, collapsed by default and open withopen="true", keyboard operable, and the print control opens it so the code reaches the paper, where the card is one white block with no copy control. - A page whose root carries no
data-themeshows no copy button and every block reads normally. -
pnpm run testpasses, including cleantest:html, the one-h1check andtest:kanban, andnpx stylelint "be-best/assets/css/*.css"is clean under the theme’s own config.pnpm stylelint "**/*.css"is not a gate this repository meets: it also lints the generated stylesheet, and Tailwind’s output has carried 968 of those complaints since before this card, 1027 with this card’s file added.