GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

9 Code Block UI Patterns — No Highlighter Library

A code block UI is the small frame built around a snippet — header, copy button, tabs — that tells a reader what they are looking at before they touch a single

Auto-plays · click a tile to jump to its section · all nine in one zip

None of the nine below need an extra library; every color you see comes from plain classes in the markup, with nothing to import. The order follows how a snippet actually gets used on a page rather than how flashy each pattern looks. First it has to be copyable in one click (01), then show the same example in more than one syntax (02). Once a reader is inside the block, attention narrows to one exact line (03) or to what changed between two versions (04). A block that runs long needs a way to fold away (05), and a page with several files needs a header that says which one is open right now (06). Beyond a static snippet, a command can type and answer itself (07), a result can sit right next to the code that drew it (08), and a single unfamiliar word inside a paragraph can explain itself the moment someone points at it (09). What makes a copy code button ui worth building is not the icon but the moment the reader actually takes the code with them. The neighbor on the embed axis that builds even the play button is Custom Video Player CSS Controls, and the list-shaped one stacking in a sidebar is Activity Feed UI Widgets. Line numbers recompute their gutter width the moment they pass three digits, and when two or more lines are highlighted the deepest tint stays on the topmost one so the eye anchors somewhere. The copy button's checkmark reverts after 1.2 seconds, long enough to read without hiding the code it sits over. Tabs keep their height across languages by reserving the tallest label's baseline, so switching from a short CSS example to a longer JavaScript one never jumps.

01Copy button that says it copied

What you see

Pressing the button in the top-right corner hands the snippet to navigator.clipboard and swaps the label in a single frame, with no fade in between.

Copy button that says it copied
Open in a new tab

01 · Copy button that says it copied — Measured area 28.962% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · See the password

navigator.clipboardaria-livesteps(1, end)

Where to look in the code

A hidden aria-live region repeats the new label out loud, so someone using a screen reader hears that the copy went through, too.

btn.addEventListener('click', function () {
  root.classList.remove('is-demo');   // a human press stops the auto loop
  if (navigator.clipboard && navigator.clipboard.writeText) {
    navigator.clipboard.writeText(codeText.textContent).catch(function () {});
  }
  btn.classList.add('is-copied');
  live.textContent = 'Copied';
  clearTimeout(timer);
  timer = setTimeout(function () {
    btn.classList.remove('is-copied');
    live.textContent = '';
  }, 1600);
});

02Tabs that swap the same example per language

How the code snippet ui tabs behave on screen

Three tabs show one example in CSS, SCSS, and Tailwind, and a bar underneath slides to whichever tab is active.

Tabs that swap the same example per language
Open in a new tab

02 · Tabs that swap the same example per language — Measured area 2.667% · moving frames 22/23Original by GODRICH · SCSS and React ship in the zip · See the password

translateXaria-selectedtablist

How to read the code

That bar is not a fixed width — it is measured from each tab's own content, so a short label and a long one get different underline widths.

@keyframes lts-indicator {
  0%   { transform: translateX(0) scaleX(1); }
  30%  { transform: translateX(43.2px) scaleX(1.14); }
  60%  { transform: translateX(92.58px) scaleX(1.55); }
  88%  { transform: translateX(0) scaleX(1); }
  100% { transform: translateX(0) scaleX(1); }
}

03Line numbers with a called-out line

What moves

Line numbers here are not typed text; a CSS counter prints them onto a pseudo-element, so a reader who copies the snippet never drags a stray number along with it.

Line numbers with a called-out line
Open in a new tab

03 · Line numbers with a called-out line — Measured area 14.751% · moving frames 22/23Original by GODRICH · SCSS and React ship in the zip · See the password

counter-increment::beforeuser-select

What to change

Hovering or tabbing to the called-out line brings up a small note explaining why that one line matters.

.ln::before {
  counter-increment: ln;
  content: counter(ln);
  position: absolute;
  left: 0;
  top: 0;
  width: 16px;
  text-align: right;
  color: $syn-dim;
  user-select: none;
}

04Diff that splits added and removed lines

What you see

Added lines carry a green edge and removed lines a red one, and the plus or minus sign sits in its own span so only the real code ever gets copied.

Diff that splits added and removed lines
Open in a new tab

04 · Diff that splits added and removed lines — Measured area 28.963% · moving frames 17/23Original by GODRICH · SCSS and React ship in the zip · See the password

aria-expandedborder-inline-startsteps(1, end)

Where to look in the code

A stretch where nothing changed folds into one row until a reader chooses to open it.

var cd = document.getElementById('cd');
var fold = document.getElementById('cd-fold');
fold.addEventListener('click', function () {
  cd.classList.remove('is-demo');
  var open = fold.getAttribute('aria-expanded') === 'true';
  fold.setAttribute('aria-expanded', String(!open));
  fold.setAttribute('aria-label', open ? 'Expand hidden lines' : 'Collapse hidden lines');
  cd.classList.toggle('is-open', !open);
});

05Long block that folds until you open it

What happens on screen

A long file opens by moving grid-template-rows from 0fr to 1fr, so there is no fixed pixel height to measure and hard-code ahead of time.

Long block that folds until you open it
Open in a new tab

05 · Long block that folds until you open it — Measured area 31.724% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

grid-template-rowsaria-expandedlinear-gradient

How to read the code

While it stays folded, a fading band sits over the bottom edge as a hint that more code is waiting below.

.ecb__more {
  display: grid;
  grid-template-rows: 0fr;
}
.ecb:not(.is-demo) .ecb__more {
  transition: grid-template-rows $duration $easing;
}
.ecb.is-open .ecb__more { grid-template-rows: 1fr; }

06Window header that carries the file name

What moves

Three dots, a file icon, and a file name sit in the header, so a reader knows where to paste before reading a single line of the panel below.

Window header that carries the file name
Open in a new tab

06 · Window header that carries the file name — Measured area 5.082% · moving frames 17/23Original by GODRICH · SCSS and React ship in the zip · See the password

aria-selectedborder-radiustranslateY

What to change

With more than one file, that same header becomes the tab strip, and the file name switches to match whichever tab is open.

function fwSelect(i) {
  root.classList.remove('is-demo');
  // the header file name follows the active tab — no lying header
  titleEl.textContent = tabs[i].textContent;
  tabs.forEach(function (t, idx) {
    var on = idx === i;
    t.setAttribute('aria-selected', on ? 'true' : 'false');
    t.tabIndex = on ? 0 : -1;
  });
  panels.forEach(function (p, idx) {
    var on = idx === i;
    p.classList.toggle('is-active', on);
    p.setAttribute('aria-hidden', on ? 'false' : 'true');
  });
}

07Terminal that types the command itself

What you see

One command grows a character at a time and a block cursor blinks at the end of it, then two lines of output rise underneath once the typing finishes.

Terminal that types the command itself
Open in a new tab

07 · Terminal that types the command itself — Measured area 2.603% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · See the password

steps(24, end)animation-fill-modemonospace

Where to look in the code

The whole window doubles as a button — pressing it, or reaching it with Tab and Enter, replays the same typing for real.

@keyframes term-type {
  0%   { width: 0ch;  animation-timing-function: steps(24, end); }
  44%  { width: 24ch; animation-timing-function: steps(1, end); }
  86%  { width: 24ch; animation-timing-function: steps(1, end); }
  100% { width: 0ch; }
}

08Pane that flips between code and result

What happens on screen

One pane swaps between the code and the thing that code draws, and the swap happens in a single frame so the two layers never show through each other.

Pane that flips between code and result
Open in a new tab

08 · Pane that flips between code and result — Measured area 11.172% · moving frames 16/23Original by GODRICH · SCSS and React ship in the zip · See the password

aria-pressedsteps(1, end)visibility

How to read the code

The active side also lives in aria-pressed, so it is clear which of the two buttons is on even without seeing the highlight.

function setView(showResult) {
  root.classList.remove('is-demo');
  codeBtn.setAttribute('aria-pressed', String(!showResult));
  eyeBtn.setAttribute('aria-pressed', String(showResult));
  codeBtn.classList.toggle('is-on', !showResult);
  eyeBtn.classList.toggle('is-on', showResult);
  codeFace.style.opacity = showResult ? '0' : '1';
  codeFace.style.visibility = showResult ? 'hidden' : 'visible';
  resultFace.style.opacity = showResult ? '1' : '0';
  resultFace.style.visibility = showResult ? 'visible' : 'hidden';
  codeFace.setAttribute('aria-hidden', String(showResult));
  resultFace.setAttribute('aria-hidden', String(!showResult));
}

09Inline code that explains a token on hover

What moves

A word sitting inside a sentence carries a dotted underline, and hovering or tabbing to it pops up a short note explaining what that word means.

Inline code that explains a token on hover
Open in a new tab

09 · Inline code that explains a token on hover — Measured area 10.227% · moving frames 11/23Original by GODRICH · SCSS and React ship in the zip · See the password

aria-describedbytext-decorationfocus-visible

What to change

Because the note is positioned absolutely, it never pushes the paragraph below it downward, which means the space for it has to be reserved ahead of time instead.

.tp__body {
  position: relative;
  box-sizing: border-box;
  padding: $sp-3;
  padding-bottom: $sp-10; // pitfall 7 — the popover is position:absolute, so it adds nothing to the root height. reserve it up front
  font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
  font-size: 12px;
  line-height: 18px;
}

Where it breaks — the trap

The sliding bar under the language tabs in 02 is not eyeballed. A Playwright measurement of the real tab boxes puts CSS at 43.2px starting at 0, SCSS at 49.38px starting at 43.2px, and Tailwind at 67.05px starting at 92.58px — three different widths because "Tailwind" is simply a longer label than "CSS". Hardcode one bar width for all three tabs and it either overshoots the short label or stops short of the long one; only reading each tab's own getBoundingClientRect at every swap keeps the bar honest.

The result pane in 08 rounds its own corners with overflow: hidden, which is ordinary for a card — until a code line runs long. One line inside that panel is wider than the 288px card, and the rounded-corner clip quietly cuts its right edge off. A page-level scroll check never catches this, because the surrounding document itself never overflows; only opening the still frame and reading the last few characters on that line shows the cut.

The toggle in 05 used to flip its caret and its "Show more" label back partway through the closing animation, while the panel underneath was still visibly open on screen — for a few hundred milliseconds the label was announcing a state the panel had not reached yet. Moving both back at 84% — right as the fold actually finishes, rather than partway through it — closed that gap between what the eye sees and what the label claims.

The note in 09 is position: absolute, so it never counts toward its parent's height — on a narrow phone width, skipping the reserved padding below the sentence would let the note land on top of the code sample it is meant to explain. Preparing for strings that grow per language inside the window wants the checks of i18n JSON Checks first. Every one of these nine sits together in one folder, and the archive holding all nine is opened with the same password, 8j8vmbbq, folded into this sentence rather than boxed off on its own line.

Accessibility (reduced-motion)

prefers-reduced-motion: reduce turns off every autoplay loop across all nine, and what is left standing afterward is a chosen resting state, never a frame frozen mid-motion. Keyboard reach and screen-reader labels run on their own track, independent of whether motion is on, since most of these are native buttons underneath the animation.

Item Reduced motion leaves Keyboard Screen reader
01 Copy button Idle label showing, cursor hidden Native button, Tab then Enter aria-live announces the new label
02 Language tabs CSS tab selected, bar at rest Arrow Left/Right moves focus and swaps aria-selected marks the active tab
03 Line highlight Tooltip and cursor hidden until touched Tab focuses the called-out line aria-describedby ties the line to its note
04 Diff fold Fold open, added/removed bars fully drawn Native button, Tab then Enter aria-expanded marks the fold state
05 Collapsed block Panel collapsed, cursor hidden Native button, Enter or Space aria-expanded plus a labeled toggle
06 Filename header Locks to the second tab and its file name Arrow Left/Right, roving tabindex aria-selected and the header text move together
07 Terminal Cursor stops blinking, command and output stay filled in role="button", Enter or Space replays it aria-label announces "다시 재생"
08 Result split Code face visible, result face hidden Two native buttons, Tab then Enter aria-pressed marks which face is on
09 Token popover Both notes hidden until touched Tab reaches each token, Escape closes aria-describedby ties token to note

Item 01's clipboard write only runs inside a secure context in the first place, which is one more reason the fallback there is a silent no-op rather than a broken button. For the click-triggered half of this family — buttons and toggles that change state on a tap rather than a hover — the click trigger index collects the rest, and the tabbed pattern in 02 has a longer write-up in the CSS tab menu transition post.

FAQ

Does a code block ui design still need a highlighting library like Prism?

No — every color across these nine comes from a plain span with a class, hand-picked for one fixed snippet. A real highlighting library still earns its keep once the code being shown is arbitrary text a user pasted in, since hand-tagging every keyword does not scale beyond a small set of known examples. Going from showing code to editing it is the next step in Code Editor JavaScript.

Why does the copy button in 01 do nothing on some pages?

navigator.clipboard.writeText only runs inside a secure context, so a page served over plain HTTP gets a rejected promise and nothing else happens. See MDN's Clipboard API page for the exact requirement, which is also why the demo wraps the call in a catch that does nothing rather than showing an error.

Can the tab indicator in 02 just use one fixed width instead of measuring?

Only if every label happened to be the same length — and "CSS", "SCSS", and "Tailwind" are not. The moment labels differ, a fixed-width bar either overshoots the short one or falls short of the long one — exactly the gap the real getBoundingClientRect numbers in the trap section above close. Related copy-and-paste chrome, including the button pattern behind 01, lives in the content category and the button state feedback post.

Enter the archive password

The password is inside this article. You will find it as you read.