etchdeveloperDemoGet the component
Demo

Code Block

Code where it usually lives: a tutorial, a product page, a code review, configuration files, a snippet library and more. Copy, download, expand and wrap it, and compare the themes.

21 code blocks on one page, in all 13 languages. Brand names, code and copy are placeholders. Every block is the same component with different settings.

A tutorial in a blog post

Theme Auto, the default · File names · Line numbers and Highlight lines 8-13 in the CSS · Collapsed lines 10 and Download in the JavaScript.

Tutorial

Add a dark mode switch in three small files

Lorem ipsum dolor sit amet, consectetur adipiscing elit. A button in the header, two palettes in CSS custom properties and a few lines of JavaScript that remember what the visitor chose.

1. The button

Put the button in your header. Its pressed state tells screen readers whether dark mode is on. Integer posuere erat a ante venenatis dapibus.

header.html
<button class="theme-switch" type="button" aria-pressed="false">
  <svg aria-hidden="true" width="20" height="20" viewBox="0 0 24 24">
    <path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
  </svg>
  <span>Dark mode</span>
</button>

2. Two palettes

Light colors are the default. The highlighted lines switch them as soon as the page carries the dark theme attribute. Donec ullamcorper nulla non metus auctor fringilla.

theme.css
:root {
  --page-bg: #ffffff;
  --page-text: #1d2939;
  --accent: #3659ae;
  color-scheme: light;
}

:root[data-theme="dark"] {
  --page-bg: #101828;
  --page-text: #f2f4f7;
  --accent: #a9c2ff;
  color-scheme: dark;
}

body {
  background: var(--page-bg);
  color: var(--page-text);
}

3. Remember the choice

The script reads the saved choice, falls back to the system setting and updates the button. It is longer than the preview, so the block starts collapsed: expand it, or copy and download the whole file.

theme-switch.js
const STORAGE_KEY = 'theme';
const root = document.documentElement;
const button = document.querySelector('.theme-switch');

function preferredTheme() {
  const saved = localStorage.getItem(STORAGE_KEY);
  if (saved === 'light' || saved === 'dark') return saved;
  return matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}

function applyTheme(theme) {
  root.dataset.theme = theme;
  button?.setAttribute('aria-pressed', String(theme === 'dark'));
}

button?.addEventListener('click', () => {
  const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
  localStorage.setItem(STORAGE_KEY, next);
  applyTheme(next);
});

applyTheme(preferredTheme());

That’s it. Cras mattis consectetur purus sit amet fermentum: whatever readers copy is the complete original file, never just the visible preview.

A product page with a dark hero

Theme Dark · Icons toolbar: hover or focus a button to see its label · The one-line install command hides the language badge and the wrap button and uses Text size Small.

Lorem Events API

Send your first event in five lines

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Install the package, add your key and track what matters: no dashboards to configure first.

Free for 10,000 events a month. Maecenas faucibus mollis interdum.

Install
npm install @lorem/events
Quick starttrack.js
import { Events } from '@lorem/events';

const events = new Events(process.env.LOREM_KEY);

await events.send('signup', { plan: 'pro' });

Code review

First line number 41 · Removed lines 44 · Added lines 45-46 · Caption below the code.

Fix: free shipping thresholdinc/shipping.php
function lorem_free_shipping( $rates, $package ) {
    $total = WC()->cart->get_subtotal();

    if ( $total > 100 ) {
    $threshold = (float) apply_filters( 'lorem_free_shipping_threshold', 100 );
    if ( $total >= $threshold ) {
        unset( $rates['flat_rate:1'] );
    }

    return $rates;
}
Lines 41–51 of inc/shipping.php: the fixed limit of 100 becomes a filter.

A patch file

Language Diff colors the − and + lines and the hunk header · Download saves fix-shipping-threshold.diff · Theme Light.

fix-shipping-threshold.diff
--- a/inc/shipping.php
+++ b/inc/shipping.php
@@ -42,7 +42,8 @@ function lorem_free_shipping( $rates, $package ) {
     $total = WC()->cart->get_subtotal();
 
-    if ( $total > 100 ) {
+    $threshold = (float) apply_filters( 'lorem_free_shipping_threshold', 100 );
+    if ( $total >= $threshold ) {
         unset( $rates['flat_rate:1'] );
     }
 
     return $rates;

Configuration files

Icons toolbar on all three · YAML with a long file name · JSON indented with tabs at Tab size 2, collapsed after 8 lines · Plain text with Download.

Deploy workflow

A GitHub Actions workflow. Its secret placeholder is shown exactly as written.

.github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build
      - run: rsync -az dist/ deploy@lorem.example:/var/www/site
        env:
          SSH_KEY: ${{ secrets.DEPLOY_KEY }}

Block theme settings

Indented with tabs and shown at Tab size 2. Collapsed after 8 lines.

theme.json
{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 3,
	"settings": {
		"color": {
			"palette": [
				{
					"slug": "ink",
					"color": "#1d2939",
					"name": "Ink"
				},
				{
					"slug": "paper",
					"color": "#ffffff",
					"name": "Paper"
				}
			]
		},
		"typography": {
			"fluid": true
		},
		"layout": {
			"contentSize": "68ch",
			"wideSize": "1200px"
		}
	}
}

Environment

Plain text with Download, so the file arrives ready to rename.

.env.example
APP_URL=https://lorem.example
DB_HOST=127.0.0.1
DB_NAME=lorem
DB_USER=lorem
DB_PASSWORD=change-me
MAIL_FROM=hello@lorem.example

A database report

Language SQL · Icons toolbar · Line numbers · Wrap lines on: the long WHERE line wraps under one number, and the reader can turn wrapping off.

Monthly revenue by plan
SELECT
  DATE_TRUNC('month', o.created_at) AS month,
  p.name AS plan,
  COUNT(*) AS orders,
  SUM(o.total) AS revenue
FROM orders AS o
JOIN plans AS p ON p.id = o.plan_id
WHERE o.status = 'paid' AND o.created_at >= DATE '2026-01-01' -- this year only; refunded orders drop out through the status filter
GROUP BY month, plan
ORDER BY month, revenue DESC;

A README

Language Markdown · Icons toolbar · File name README.md · Caption below the code.

README.md
# Lorem Starter

A small starter theme for **Etch** sites. Lorem ipsum dolor sit amet.

## Install

1. Download the [latest release](https://lorem.example/releases).
2. Upload it in *Appearance → Themes*.
3. Run `npm install` in the theme folder.

> Tip: keep your own changes in a child theme.
Markdown is colored too: headings, emphasis, lists, links, quotes and inline code.

A snippet library from a loop

Four snippets, one block: Code, Language and File name come from a JSON loop. A dynamic binding that is the whole value needs no preparation, so this works the same with an ACF field or a custom field.

Fluid type mixin

SCSS: one mixin for type that scales between two sizes.

_fluid-type.scss
@use 'sass:math';

@mixin fluid-type($min, $max, $from: 320px, $to: 1280px) {
  $slope: math.div($max - $min, $to - $from);
  font-size: clamp(#{$min}, #{$min - $slope * $from} + #{$slope * 100}vw, #{$max});
}

.lead {
  @include fluid-type(1.125rem, 1.5rem);
}

Debounce helper

TypeScript: wait until the input settles before reacting.

debounce.ts
export function debounce<T extends unknown[]>(fn: (...args: T) => void, wait = 200) {
  let timer: ReturnType<typeof setTimeout> | undefined;
  return (...args: T) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), wait);
  };
}

Body class for covers

PHP: mark posts with a featured image for your CSS.

functions.php
add_filter( 'body_class', function ( array $classes ): array {
    if ( is_singular( 'post' ) && has_post_thumbnail() ) {
        $classes[] = 'has-cover';
    }
    return $classes;
} );

Nightly database backup

Bash: export the WordPress database with WP-CLI and compress it.

db-export.sh
#!/usr/bin/env bash
set -euo pipefail

stamp=$(date +%F)
wp db export "backups/db-${stamp}.sql" --add-drop-table
gzip -9 "backups/db-${stamp}.sql"
echo "Saved backups/db-${stamp}.sql.gz"

Themes

Icons toolbar · Light and Dark are fixed. Site follows the color scheme of its section. Auto, the default, follows the visitor’s system setting: switch your system to dark mode and the tutorial above turns dark.

Light

A fixed light palette, whatever the visitor’s system uses.

button.css
.button {
  padding: 0.75em 1.25em;
  border-radius: 999px;
  background: var(--accent);
  color: white;
}

Dark

A fixed dark palette, here on a light page.

button.css
.button {
  padding: 0.75em 1.25em;
  border-radius: 999px;
  background: var(--accent);
  color: white;
}

Site

Follows the color scheme of the section around it. This dark box declares a dark color scheme, as an ACSS dark section does.

button.css
.button {
  padding: 0.75em 1.25em;
  border-radius: 999px;
  background: var(--accent);
  color: white;
}

Your own look

Eight styling variables on a parent class: square corners, a 2 px border, a hard shadow, a tinted toolbar and a bolder title, here with the Icons toolbar. The code is this example’s own CSS.

The CSS of this example
.etchdev-demo__brandcode {
  --ed-code-block-radius: 0;
  --ed-code-block-border-width: 2px;
  --ed-code-block-border-color: var(--text-dark);
  --ed-code-block-divider-color: var(--text-dark);
  --ed-code-block-shadow: 6px 6px 0 var(--primary);
  --ed-code-block-toolbar-bg: var(--primary-ultra-light);
  --ed-code-block-button-radius: 0;
  --ed-code-block-title-weight: 800;
}

Polish interface

All 22 interface labels in Polish, set in the Interface labels group: Kopiuj kod, Pobierz kod, Rozwiń kod. Highlight lines 3 · Collapsed lines 5.

Licznik słówlicznik-slow.js
// Policz słowa w tekście
export function countWords(text) {
  const words = text.trim().split(/\s+/);
  return words.filter(Boolean).length;
}

console.log(countWords('Ala ma kota')); // 3

End of the demo. Get the Code Block component