Skip to content

Config

Every table on this page is generated from the library on each build: keys, types, defaults and descriptions from src/config/types.ts and src/tty/types.ts, and the mode each key works in from the validator itself. The guides show these keys in use; start with Web apps or Terminal apps.

A config file (showcase.config.ts, .mts, .mjs or .js) exports its config as the default export, usually through defineConfig, which only adds type checking and editor completion:

showcase.config.mjs
import { defineConfig } from '@noctcore/showcase-kit';
export default defineConfig({
name: 'My App',
target: { mode: 'url', url: 'http://localhost:5173', start: 'pnpm dev' },
shots: [{ id: 'home', title: 'Home', nav: '/' }],
});

target.mode decides the kind of config. 'url' and 'cdp' capture a web page (web in the tables below); 'tty' runs a terminal app (tty). A key marked web only or tty only works in that kind alone; an unmarked key works in both.

The config is checked before anything runs, and every problem is reported at once, each with its path, so one run shows the whole list:

Invalid showcase config (showcase.config.mjs):
- config.viewPort: unknown key (expected one of: name, slug, root, target, ready, viewport, ...)
- shots[1].id: duplicates another shot id "home"
- frame.padding: must be an integer >= 0, got "72"
  • Unknown keys are errors, so a typo such as viewPort is not silently ignored. The message lists the keys that object accepts (config stands for the top level).
  • A key of the other mode gets its own message, such as config.viewport: not used in tty mode (the image size comes from ...). The full list is in Keys in the wrong mode.
  • Relative paths resolve against the config file’s directory, or against root when you set it (root itself is relative to the config file’s directory). That covers the outputs paths, outputs.portfolio.dir and gallery, target.cwd, hero.logo and the terminal.font files.
  • Path templates only take the tokens listed in Path templates, and must contain the ones that keep files apart.

From code, resolveConfig(object, rootDir) runs the same checks on a config object: see the programmatic API.

KeyTypeDefaultMeaning
namestringrequiredApp name, used in frame titles and alt text.
slugstringthe name, lowercased, with dashes for everything elseUsed for the {slug} token. Letters, digits, - and _.
rootstringthe config file's directoryBase directory for relative paths, itself relative to the config file's directory.
deviceScaleFactornumber2Device pixels per CSS pixel, 0.25 to 4: every image is this many times its CSS pixel size.
langsstring[]['en']Languages to capture, in order. Each gets its own setup run.
frameFrameOptionsper keyThe window frame around the README images. Portfolio images, the hero and clips use the same look.
outputsOutputsper keyWhere the files go.
heroHeroOptionsper keyBanner image for the top of a README: logo, name, tagline and framed shots, in one of several layouts.
browserBrowserOptionsper keyThe Chromium that captures web apps, renders terminal screens and draws frames.
timeoutsTimeoutsper keyHow long to wait for the app and for each shot.
targetweb onlyWebTargetrequiredHow to reach the app: mode: 'url' or mode: 'cdp'.
targettty onlyTtyTargetrequiredHow to run the app: mode: 'tty'.
readyweb onlystringnoneSelector that exists once the app has booted.
readytty onlystring | RegExpnoneText on screen once the app has drawn: a substring or a RegExp.
viewportweb only{ ... }{ width: 1440, height: 900 }CSS pixel size of every capture.
colorSchemeweb only'light' | 'dark' | 'no-preference''dark'The prefers-color-scheme the page sees.
cssweb onlystringnoneExtra CSS injected before each shot, for example to hide dev overlays.
setupweb only(ctx: SetupContext) => Promise<void> | voidnoneRuns once per language after the app is ready: seed fixtures, switch locale, dismiss dialogs.
setuptty only(ctx: TtySetupContext) => Promise<void> | voidnoneRuns in each new app process once it is ready, before its first shot or clip.
shotsweb onlyShot[]requiredThe views to capture, in order.
shotstty onlyTtyShot[]requiredThe screens to capture, in order, in one app process per language.
terminaltty onlyTerminalOptionsper keyHow the terminal looks: theme, font, line height, padding, cursor.
clipstty onlyClip[]per keyAnimated recordings, written by showcase record (and showcase all).
KeyTypeDefaultMeaning
widthnumberrequiredWidth in CSS pixels, 16 to 8192.
heightnumberrequiredHeight in CSS pixels, 16 to 8192.
Which Chromium the kit launches, and how.
KeyTypeDefaultMeaning
channelstringnoneA Playwright channel such as chrome or msedge, to use an installed browser instead of a downloaded one.
executablePathstringnonePath to a Chromium based browser to launch instead of the downloaded Chromium.
headlessbooleantrueRun the browser without a window.
argsstring[]noneExtra command line arguments for the browser.
How long to wait, in milliseconds.
KeyTypeDefaultMeaning
readyMsweb onlynumber30000Wait for the ready selector, in milliseconds. Not used in tty mode (see target.readyTimeoutMs).
shotMsnumber15000Navigation and waitFor per shot, in milliseconds. In tty mode it bounds each waitFor and clip waitFor step.
networkIdleMsweb onlynumber3000Best-effort network idle wait, in milliseconds; a busy dev server only costs this much. Not used in tty mode.

How the kit reaches the app. The mode picks one of three shapes.

A web app the kit opens in its own headless Chromium, starting it first if needed.
KeyTypeDefaultMeaning
mode'url'requiredCapture the app in the kit's own headless Chromium.
urlstringrequiredThe app's URL. nav paths that start with / resolve under its path: it is the app's base directory.
startstringnoneShell command that starts the app (for example pnpm dev:web). Omit if it is already running.
cwdstringthe config rootWorking directory for start, relative to the config root.
envRecord<string, string>noneExtra environment for start.
readyTimeoutMsnumber60000How long to wait for url to answer after start, in milliseconds.
reuseExistingbooleantrueUse an app that already answers on url instead of starting a second copy.
A running Chromium based app the kit attaches to over the DevTools Protocol.
KeyTypeDefaultMeaning
mode'cdp'requiredAttach to a running Chromium based app (Electron, or WebView2 on Windows) over the DevTools Protocol.
cdpUrlstring'http://127.0.0.1:9222'The app's remote debugging endpoint.
pageMatchstring | RegExpthe first page that is not a devtools pagePicks the page to capture: a substring of its URL, or a RegExp.
startstringnoneShell command that launches the app with remote debugging on. Omit if it is already running.
cwdstringthe config rootWorking directory for start, relative to the config root.
envRecord<string, string>noneExtra environment for start.
readyTimeoutMsnumber60000How long to wait for the CDP endpoint to answer after start, in milliseconds.
Runs a terminal app in a pseudo terminal and captures its screen. Needs @lydell/node-pty (or node-pty).
KeyTypeDefaultMeaning
mode'tty'requiredRun a terminal app in a pseudo terminal.
commandstring | [file: string, ...args: string[]]requiredCommand to run: a string goes through the shell like start; an array [file, ...args] is spawned directly.
cwdstringthe config rootWorking directory, relative to the config root.
envRecord<string, string> | ((ctx: { lang: string }) => Record<string, string>)noneExtra environment. A function gets the language, for apps that take their locale from an env var.
inheritEnvboolean | string[]trueWhich of your environment variables the app inherits. true: all but CI and terminal hints. false or []: only what the platform needs to start a program (PATH; on Windows also PATHEXT, SystemRoot, ComSpec). An array of names: those as well.
colsnumber120Terminal width in columns, 10 to 500.
rowsnumber32Terminal height in rows, 5 to 200.
quitKeystring | false'q'Key sent to quit before the process tree is killed, parsed like keys. false just kills.
inputDelayMsnumber300Grace after ready, before setup and the first key, for apps that enter raw mode after drawing, in milliseconds.
readyTimeoutMsnumber30000How long to wait for the ready text, in milliseconds.

The views (or terminal screens) to capture, in order. Web and tty shots share most keys; the rest are marked web only or tty only.

KeyTypeDefaultMeaning
idstringrequiredFile name stem and {id} token. Letters, digits, - and _.
titlestringthe shot's idHuman name, used in frame titles and gallery alt text.
captionstringthe shot's titleCaption under the image in the README table and the portfolio gallery.
altstringthe app name and the title, as <name>: <title>Alt text.
navweb onlyNavper keyHow to reach the view: a selector to click, a path or URL to visit, { click }, { goto } or your own steps.
navtty onlyTtyNavFn = (tty: TtySession) => Promise<void> | voidnoneOr your own steps with the session. Use keys or nav, not both.
waitForweb onlystringnoneSelector to wait for, until it is visible, after navigating.
waitFortty onlystring | RegExpnoneText on screen to wait for after the keys: a substring or a RegExp.
delayMsweb onlynumber0Extra settle time after navigating, in milliseconds.
delayMstty onlynumber0Extra settle time after waitFor, in milliseconds.
keystty onlyKeys = string | string[]noneKeys to press: plain text is typed, names in braces are keys ({Tab}, {Down}, {Enter}, {C-c}).
restarttty onlybooleanfalseStart a fresh app process (and run setup again) before this shot.

How to reach a shot.

  • A string starting with / (but not //) or http(s):// is visited as a URL. In url mode a path resolves under the target URL's path (/docs/ with https://x.io/app/ visits https://x.io/app/docs/); in cdp mode it resolves against the captured page's origin.
  • Any other string is a selector to click.
  • { click } and { goto } say which one explicitly.
  • A function receives the page and does whatever it needs.
FormMeaning
stringA selector to click, or a path or URL to visit, told apart as above.
{ click: string }A selector to click.
{ goto: string }A path or URL to visit.
NavFnYour own steps with the page.
How a capture is framed for the README, the portfolio, the hero and clips.
KeyTypeDefaultMeaning
styleFrameStyle = 'window' | 'minimal' | 'none' | 'browser' | 'windows' | 'terminal''window'window: title bar with traffic lights. minimal: thin bar. none: just the rounded screenshot. browser: a browser toolbar with the address in its address bar (url and cdp mode). windows: a Windows title bar, the title on the left and the caption buttons on the right. terminal: a terminal emulator's bar, a tab with the title in a monospace font; in tty mode it takes the terminal's background, so bar and screen read as one.
theme'light' | 'dark''dark'Title bar colors.
titlestring | false'{name}'Title bar text. Tokens: {name}, {title}, {id}, {lang}. false hides it.
addressstring'{url}'Address bar text for style: 'browser'. Tokens: {url}, {name}, {title}, {id}, {lang}. {url} is the page the shot visits without http:// or https://: the target url, resolved with the shot's nav when that is a path or URL. In cdp mode the page is not known when framing, so write the text without {url}.
backgroundBackground{ type: 'gradient', from: '#0f766e', to: '#1e1b4b', angle: 135 }What the window sits on.
paddingnumber72Space around the window, in CSS pixels.
radiusnumber14Window corner radius, in CSS pixels.
shadowbooleantrueSoft drop shadow under the window.
qualitynumber90WebP quality, 1 to 100.
maxWidthnumbernoneDownscale README images wider than this many pixels.
What a frame or the hero sits on.
FormMeaning
stringA CSS color, the same as { type: 'solid', color }.
{ type: 'solid'; color: string }One CSS color.
{ type: 'gradient'; from: string; to: string; angle?: number }A linear gradient between two CSS colors.
angle (optional): Angle in degrees, -360 to 360. Default: 135.
{ type: 'transparent' }No background: the space around the window stays transparent.
{ type: 'mesh'; colors: string[] }A mesh gradient: the first color underneath, each other color glowing from its own corner.
colors: Two to five CSS colors: the base, then the top left, top right, bottom right and bottom left glows.
{ type: 'dots'; color: string; dot?: string; spacing?: number }A subtle grid of dots on one CSS color.
color: The color under the dots.
dot (optional): The dot color. Pick a dark one on a light color. Default: 'rgba(255,255,255,0.14)'.
spacing (optional): Distance between dots in CSS pixels, 8 to 96. Default: 24.
{ type: 'noise'; from: string; to: string; angle?: number; amount?: number }A linear gradient with a film grain on top, the same on every run.
angle (optional): Angle in degrees, -360 to 360. Default: 135.
amount (optional): How strong the grain is, 0 to 1. Default: 0.2.
Where each kind of file is written, relative to the config root.
KeyTypeDefaultMeaning
rawstring'showcase-out/raw/{lang}/{id}.png'Raw capture path (PNG). Tokens: {lang}, {id}, {slug}.
readmestring | false'assets/showcase/{lang}/{id}.webp'Framed README image path (.webp or .png). Tokens: {lang}, {id}, {slug}. false skips the README images, for configs that only export a portfolio (which renders from the raw captures).
portfolioPortfolioOutputper keyPortfolio export: fixed-size images, a thumbnail and a gallery JSON. Off unless set.
clipstty onlystring'assets/showcase/{lang}/{id}.{ext}'Clip path, ending in .{ext}. Tokens: {lang}, {id}, {slug}, {ext}.
Fixed-size images for a portfolio site, a thumbnail and a gallery JSON.
KeyTypeDefaultMeaning
dirstringrequiredOutput directory. Token: {slug}.
size[number, number][1920, 1080]Exact output size in pixels, 16 to 8192 a side.
format'webp' | 'png''webp'Image format.
qualitynumber90WebP quality, 1 to 100.
thumbnailstringthe first shotShot id copied to thumbnail.<format>.
langstringthe first of langsWhich language to export.
publicPathstring'/projects/{slug}'URL prefix used for src in showcase.gallery.json. Token: {slug}.
paddingnumber96Minimum space around the window, in output pixels.
gallerystring | falseshowcase.gallery.json in dirWhere to write the gallery JSON, relative to the config root (token {slug}, must end in .json), or false to skip it.
The README banner that showcase hero renders.
KeyTypeDefaultMeaning
layoutHeroLayout = 'stack' | 'spotlight' | 'split' | 'row' | 'mosaic' | 'centered''stack'How the banner is composed: stack, spotlight, split, row, mosaic or centered.
taglinestringnoneLine under the name.
logostringnoneLogo image (PNG, SVG, WebP or JPEG), relative to the config root.
shotsstring[]as many of the first shots as the layout showsShot ids to show, back to front: one to three for stack, one to four for row and mosaic, one for the others.
langstringthe first of langsWhich language's captures to use.
outputstring'assets/showcase/hero.webp'Output path (.webp or .png). Tokens: {lang}, {slug}.
size[number, number][1280, 640]Exact output size in pixels, 320 to 8192 a side. The default is the GitHub social preview size.
backgroundBackgroundthe frame's backgroundWhat the banner sits on, in the same forms as frame.background.
theme'light' | 'dark'the frame's themeText color scheme: 'dark' draws light text, 'light' dark text.
qualitynumber90WebP quality, 1 to 100.
How the terminal looks when rendered. Every field is optional in config.
KeyTypeDefaultMeaning
theme'dark' | 'light' | TerminalTheme'dark'Colors: 'dark' (Tokyo Night), 'light' (Tokyo Night Day) or your own palette.
font{ ... }per keyFont files and size.
lineHeightnumber1.32Line height as a multiple of the font size (1 to 3), rounded to whole pixels.
paddingnumber12CSS pixels between the grid and the edge of the capture, 0 to 400.
cursor'hide' | 'show''hide'Whether to draw the cursor. A shown cursor never blinks.

A custom palette instead of 'dark' or 'light':

A 16 color ANSI palette plus the default colors.
KeyTypeDefaultMeaning
backgroundstringrequiredBackground color, #rrggbb.
foregroundstringrequiredDefault text color, #rrggbb.
cursorstringthe foreground colorCursor color, #rrggbb.
ansistring[]requiredExactly 16 colors: the 8 normal ANSI colors, then the 8 bright ones.
KeyTypeDefaultMeaning
filestringthe bundled JetBrains MonoFont file (woff2, woff, ttf or otf), relative to the config root.
boldFilestringthe bundled JetBrains Mono Bold, unless file is setBold face.
italicFilestringthe bundled JetBrains Mono Italic, unless file is setItalic face.
boldItalicFilestringthe bundled JetBrains Mono Bold Italic, unless file is setBold italic face.
fallbackFilestringnoneExtra font tried for glyphs the main font lacks, for example a symbols or Nerd Font.
sizenumber15Font size in CSS pixels, 6 to 96.
A short animated recording of a terminal app, written by showcase record. Each clip starts a fresh app.
KeyTypeDefaultMeaning
idstringrequiredFile name stem and {id} token. Letters, digits, - and _; must not repeat a shot id.
titlestringthe clip's idHuman name, used in the frame title.
captionstringthe clip's titleCaption under the clip in the README table.
altstringthe app name and the title, as <name>: <title>Alt text.
stepsClipStep[]requiredThe timeline, run once the app is ready and setup has run.
fpsnumber10Frames per second, 1 to 50.
durationMsnumber60000Upper bound on the clip length in milliseconds.
maxFramesnumber300Upper bound on the frames, counted after unchanged frames merge (like RecordedClip.frames). Each distinct frame stays in memory until the clip is encoded. When a recording reaches it, the kit warns, stops recording and writes the frames so far.
tailMsnumber1500How long to keep recording after the last step, in milliseconds.
formatsClipFormat[] = ('webp' | 'gif' | 'mp4')[]['webp', 'gif']Formats to write. 'mp4' needs ffmpeg on PATH.
One step of a clip's timeline. Steps run on the clip's frame clock, so a key's effect shows from the next frame.
FormMeaning
{ keys: Keys }Keys to press, like a shot's keys.
{ type: string; delayMs?: number }Text typed literally (no {Key} names), all at once or one character every delayMs.
{ waitFor: string | RegExp }Wait until this text is on screen (a substring or a RegExp), bounded by timeouts.shotMs.
{ sleep: number }Pause, in milliseconds, rounded to whole frames.

What the validator accepts in each templated key. A path with an unknown token, a missing required token or the wrong ending is an error. {id} is required so shots do not overwrite each other, and so is {lang} once langs has more than one language.

KeyTokensMust containMust end in
frame.title{name}, {title}, {id}, {lang} (filled when the image is drawn; not checked by the validator)nothinganything
frame.address{url}, {name}, {title}, {id}, {lang}nothinganything
outputs.raw{lang}, {id}, {slug}{id}; with more than one language also {lang}.png
outputs.readme{lang}, {id}, {slug}{id}; with more than one language also {lang}.webp or .png
outputs.clips{lang}, {id}, {slug}, {ext}{id}, {ext}; with more than one language also {lang}.{ext}
outputs.portfolio.dir{slug}nothinganything
outputs.portfolio.publicPath{slug}nothinganything
outputs.portfolio.gallery{slug}nothing.json
hero.output{lang}, {slug}nothing.webp or .png

Keys the validator refuses in a mode with a message of its own, rather than “unknown key”. Generated by trying each key in each mode.

KeyRefused inMessage
terminalurl modeonly used in tty mode
clipsurl modeweb clips arrive in v0.3; clips only work with target.mode "tty" for now
viewporttty modenot used in tty mode (the image size comes from target.cols, target.rows, terminal.font.size and terminal.padding)
colorSchemetty modenot used in tty mode (set terminal.theme)
csstty modenot used in tty mode
outputs.clipsurl modeweb clips arrive in v0.3; clips only work with target.mode "tty" for now
timeouts.readyMstty modenot used in tty mode (set target.readyTimeoutMs)
timeouts.networkIdleMstty modenot used in tty mode
target.urltty modenot used in tty mode (the kit runs target.command in a terminal)
target.starttty modenot used in tty mode (target.command starts the app)
target.reuseExistingtty modenot used in tty mode
target.cdpUrltty modenot used in tty mode
target.pageMatchtty modenot used in tty mode
shots[].keysurl modeonly used in tty mode
shots[].restarturl modeonly used in tty mode
target.commandurl and cdp modeonly used in tty mode
target.inheritEnvurl and cdp modeonly used in tty mode
target.colsurl and cdp modeonly used in tty mode
target.rowsurl and cdp modeonly used in tty mode
target.quitKeyurl and cdp modeonly used in tty mode
target.inputDelayMsurl and cdp modeonly used in tty mode