defuss-shadcn / Mockup / mockup-code
CodeATM
A terminal window for commands and what they print - install steps, CLI docs, a session transcript. Each line is a <pre>; its prompt ($, >, a line number) is generated by CSS, so selecting or copying the window yields the commands alone. Tones, highlighted lines, window chrome with a title, line numbers, a blinking cursor. CSS only, after daisyUI's mockup-code - the installation and agent-skill guides use it for every command.
On this page (15)
§Prompt
data-prefix='$' draws the prompt. It is generated content: select the line or copy it - you get the command, never the $.
§Output and status
Lines after the command are its output: data-tone colours them - warning, success, info, destructive, muted - tuned for the dark surface.
§Highlighted lines
data-highlight tints a line in its tone; data-highlight='solid' fills it - the error line. Numbers as prefixes label the lines.
§Long lines
A long line scrolls the window sideways (the lines keep one width, so the scroll is shared); data-wrap wraps instead, with a hanging indent under the prompt.
§Without prefix
A line without data-prefix has no prompt - plain output, or a file.
§Colors
data-variant: muted and outline for a light terminal (the tones darken for contrast), primary for any brand colour.
§Window and title
data-chrome='mac' colours the dots, data-title puts a title in the bar, data-chrome='none' drops the bar.
§Line numbers and syntax highlighting
data-numbers numbers every line without its own prefix - CSS counters, nothing to keep in sync. The colours come from Shiki (the highlighter these docs use): codeToTokens returns one token list per line, which maps one-to-one onto the window's <pre> lines - plain text until it loads, and the numbers and the highlighted line keep working.
§Diff
data-diff='remove' and 'add' mark the lines - a generated -/+ in its own sign column, red and green bands; lines without it are context and line up. <del> and <ins> inside a line mark the words that changed. Numbers and the sign column combine.
§Animated diff
data-animate plays the change: removed lines turn red and strike through, then the added lines slide in green, one after another (sibling-index() staggers them; --mockup-code-delay / --mockup-code-stagger tune it). CSS only - the script here only starts it when the window scrolls into view and replays it on click.
§Multi-line commands
One command per line; a command continued with a backslash goes on lines with data-prefix='' - the prompt column stays empty, so everything lines up, and a copy is still a valid shell command.
§Cursor
data-cursor adds a blinking block after the line - a prompt waiting for input (still under reduced motion).
§Sizes
data-size sm · (default) · lg - text, padding and the bar scale together.
§In a right-to-left page
Commands read left to right everywhere: the window isolates its direction, so an Arabic page still shows the shell as it is typed.
§CSS view file
/* -- Mockup Code component --------------------------------------------- *//* A terminal / code window: a stack of <pre> lines under window chrome. Prompts, line numbers and the cursor are CSS-generated, so selecting and copying the text never picks them up. CSS only. */@layer components { .mockup-code { /* surface + the line tones, retuned per variant for its contrast */ --_bg: oklch(0.21 0.006 286); --_fg: oklch(0.94 0 0); --_dim: oklch(0.68 0.01 286); --_success: oklch(0.8 0.16 150); --_warning: oklch(0.84 0.15 80); --_info: oklch(0.76 0.13 245); --_destructive: oklch(0.72 0.18 25); --_px: 1.25rem; position: relative; box-sizing: border-box; min-width: 0; max-width: 100%; padding-block: 1.25rem 1rem; border-radius: var(--radius-xl); background: var(--_bg); color: var(--_fg); font-family: var(--font-mono); font-size: 0.875rem; line-height: 1.7; /* code shows what is typed: no => → ⇒ or <!-- → arrow ligatures */ font-variant-ligatures: none; text-align: start; /* code reads left to right even inside an RTL page */ direction: ltr; unicode-bidi: isolate; overflow-x: auto; overscroll-behavior-x: contain; /* one column as wide as the longest line (at least the window): every line - and its highlight band - spans the same width while the window scrolls sideways */ display: grid; grid-template-columns: minmax(100%, max-content); /* -- Window chrome: three dots; data-chrome="mac" colours them, data-chrome="none" drops the bar ---------------------------- */ &::before { content: ""; display: block; width: 0.75rem; height: 0.75rem; margin-inline-start: var(--_px); margin-block-end: 1rem; border-radius: 9999px; color: var(--_dim); background: currentColor; opacity: 0.45; box-shadow: 1.25rem 0 0 currentColor, 2.5rem 0 0 currentColor; } &[data-chrome="mac"]::before { opacity: 1; background: oklch(0.68 0.2 25); box-shadow: 1.25rem 0 0 oklch(0.82 0.16 85), 2.5rem 0 0 oklch(0.74 0.17 145); } &[data-chrome="none"] { padding-block-start: 1rem; } &[data-chrome="none"]::before { display: none; } /* -- A title in the bar: data-title="bash - 80x24" -------------- */ &[data-title]:not([data-chrome="none"])::after { content: attr(data-title); position: absolute; inset-block-start: 0.75rem; inset-inline: 4.5rem; font-family: var(--font-sans); font-size: 0.75rem; line-height: 1.25rem; text-align: center; color: var(--_dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; pointer-events: none; } /* -- Lines -------------------------------------------------- */ & > pre { box-sizing: border-box; margin: 0; padding: 0 var(--_px); border: 0; border-radius: 0; background: transparent; color: inherit; font: inherit; white-space: pre; } & > pre > code { padding: 0; border: 0; background: none; color: inherit; font: inherit; } /* the prompt: $, >, ~, a number - generated, never selected or copied */ & > pre[data-prefix]::before, &[data-numbers] > pre::before { display: inline-block; width: 2ch; margin-inline-end: 1.25ch; text-align: end; color: var(--_dim); user-select: none; } & > pre[data-prefix]::before { content: attr(data-prefix); } /* data-numbers on the window: every line without its own prefix is numbered (CSS counters - nothing to keep in sync) */ &[data-numbers] { counter-reset: mockup-line; } &[data-numbers] > pre { counter-increment: mockup-line; } &[data-numbers] > pre:not([data-prefix])::before { content: counter(mockup-line); } /* -- Tones: output lines --------------------------------------- */ & > pre[data-tone] { color: var(--_tone); } & > pre[data-tone="success"] { --_tone: var(--_success); } & > pre[data-tone="warning"] { --_tone: var(--_warning); } & > pre[data-tone="info"] { --_tone: var(--_info); } & > pre[data-tone="destructive"] { --_tone: var(--_destructive); } & > pre[data-tone="muted"] { --_tone: var(--_dim); } /* -- Highlighted line: a tinted band in the line's tone (or the text colour); data-highlight="solid" fills it -------------- */ & > pre[data-highlight] { background: color-mix(in oklch, var(--_tone, var(--_fg)) 16%, transparent); } & > pre[data-highlight="solid"] { background: var(--_tone, var(--_fg)); color: var(--_bg); &::before { color: inherit; opacity: 0.7; } } /* -- Diff: data-diff="add" / "remove" lines ---------------------- The sign sits in its own column before the code (generated, never copied), so numbers and prompts keep theirs; every line of a diff window gets the column, so context lines line up. Word changes inside a line are native <ins> / <del>. */ &:where(:has(> pre[data-diff])) > pre > code::before { content: ""; display: inline-block; width: 2ch; user-select: none; } & > pre[data-diff="add"] { --_tone: var(--_success); } & > pre[data-diff="remove"] { --_tone: var(--_destructive); } & > pre[data-diff] { color: var(--_tone); background: color-mix(in oklch, var(--_tone) 14%, transparent); } & > pre[data-diff="add"] > code::before { content: "+"; } & > pre[data-diff="remove"] > code::before { content: "-"; } & > pre > code :is(ins, del) { border-radius: 0.2em; color: inherit; text-decoration: none; } & > pre > code ins { background: color-mix(in oklch, var(--_success) 32%, transparent); } & > pre > code del { background: color-mix(in oklch, var(--_destructive) 32%, transparent); text-decoration: line-through; } /* data-strike (and the animated diff) strikes removed lines through */ &:is([data-strike], [data-animate]) > pre[data-diff="remove"] { opacity: 0.8; & > code { text-decoration: line-through; text-decoration-color: color-mix(in oklch, var(--_tone) 70%, transparent); } } /* -- Animated diff: removed lines turn red and strike through, added lines slide in green, one after another; context lines stay. Replays whenever data-animate is set again. ------------------ */ &[data-animate] > pre { --_stagger: var(--mockup-code-stagger, 140ms); --_delay: calc(var(--mockup-code-delay, 300ms) + var(--_i, 0) * var(--_stagger)); } &[data-animate] > pre[data-diff="remove"] { animation: mockup-code-remove 450ms ease-out var(--_delay) both; & > code { animation: mockup-code-strike 450ms ease-out var(--_delay) both; } } &[data-animate] > pre[data-diff="add"] { animation: mockup-code-add 450ms cubic-bezier(0.2, 0.8, 0.2, 1) var(--_delay) both; } /* -- Cursor: a blinking block after the line --------------------- */ & > pre[data-cursor] > code::after { content: ""; display: inline-block; width: 0.6em; height: 1.15em; margin-inline-start: 0.1em; vertical-align: text-bottom; background: currentColor; animation: mockup-code-cursor 1s steps(1) infinite; } /* -- Wrap instead of scrolling --------------------------------- */ &[data-wrap] { grid-template-columns: minmax(0, 1fr); } &[data-wrap] > pre { white-space: pre-wrap; overflow-wrap: anywhere; padding-inline-start: calc(var(--_px) + 3.25ch); text-indent: -3.25ch; } &[data-wrap] > pre:not([data-prefix]):not(:where([data-numbers] > pre)) { padding-inline-start: var(--_px); text-indent: 0; } /* -- Variants (surfaces) --------------------------------------- */ &[data-variant="muted"], &[data-variant="outline"] { --_fg: var(--foreground); --_dim: var(--muted-foreground); --_success: oklch(0.52 0.14 150); --_warning: oklch(0.58 0.14 70); --_info: oklch(0.52 0.14 250); --_destructive: var(--destructive); } &[data-variant="muted"] { --_bg: var(--muted); } &[data-variant="outline"] { --_bg: var(--card); border: 1px solid var(--border); } &[data-variant="primary"] { --_bg: var(--primary); --_fg: var(--primary-foreground); --_dim: color-mix(in oklch, var(--primary-foreground) 65%, var(--primary)); } /* -- Sizes ------------------------------------------------------ */ &[data-size="sm"] { font-size: 0.75rem; --_px: 1rem; padding-block: 1rem 0.75rem; } &[data-size="lg"] { font-size: 1rem; --_px: 1.5rem; } } @keyframes mockup-code-cursor { 50% { opacity: 0; } } /* each keyframe starts neutral and ends on the line's own diff styles */ @keyframes mockup-code-remove { from { color: var(--_fg); background: transparent; opacity: 1; } } @keyframes mockup-code-strike { from { text-decoration-color: transparent; } } @keyframes mockup-code-add { from { color: var(--_fg); background: transparent; opacity: 0; translate: -1.5rem 0; } } /* the line's position drives the stagger: sibling-index() where the browser has it, a counted fallback for the first 24 lines elsewhere */ @supports (order: sibling-index()) { .mockup-code[data-animate] > pre { --_i: sibling-index(); } } @supports not (order: sibling-index()) { .mockup-code[data-animate] > pre:nth-child(2) { --_i: 2; } .mockup-code[data-animate] > pre:nth-child(3) { --_i: 3; } .mockup-code[data-animate] > pre:nth-child(4) { --_i: 4; } .mockup-code[data-animate] > pre:nth-child(5) { --_i: 5; } .mockup-code[data-animate] > pre:nth-child(6) { --_i: 6; } .mockup-code[data-animate] > pre:nth-child(7) { --_i: 7; } .mockup-code[data-animate] > pre:nth-child(8) { --_i: 8; } .mockup-code[data-animate] > pre:nth-child(9) { --_i: 9; } .mockup-code[data-animate] > pre:nth-child(10) { --_i: 10; } .mockup-code[data-animate] > pre:nth-child(11) { --_i: 11; } .mockup-code[data-animate] > pre:nth-child(12) { --_i: 12; } .mockup-code[data-animate] > pre:nth-child(13) { --_i: 13; } .mockup-code[data-animate] > pre:nth-child(14) { --_i: 14; } .mockup-code[data-animate] > pre:nth-child(15) { --_i: 15; } .mockup-code[data-animate] > pre:nth-child(16) { --_i: 16; } .mockup-code[data-animate] > pre:nth-child(n + 17) { --_i: 17; } }}@media (prefers-reduced-motion: reduce) { @layer components { .mockup-code > pre[data-cursor] > code::after { animation: none; } /* the diff shows its end state at once */ .mockup-code[data-animate] > pre, .mockup-code[data-animate] > pre > code { animation: none; } }}@media (prefers-contrast: more) { @layer components { .mockup-code { --_dim: var(--_fg); border: 1px solid var(--_fg); } }}@media (forced-colors: active) { @layer components { .mockup-code { border: 1px solid CanvasText; } .mockup-code > pre[data-highlight] { forced-color-adjust: none; background: Highlight; color: HighlightText; } /* the +/- signs carry the diff; <ins>/<del> get system marks */ .mockup-code > pre > code :is(ins, del) { outline: 1px solid CanvasText; } }}Comments, ideas or improvements? Edit this page's source on GitHub