Freeframe

Documentation

Notebook

v1.0.0Updated Oct 10, 2026A personal developer blog with series, tags and code that copies

Overview#

Notebook is a personal developer blog as a WordPress block theme. The demo belongs to Imke Vos, a fictional backend engineer in Rotterdam who writes about databases, build tools and debugging. Posts are WordPress posts: a post's series is its category (posts that stand alone are in Notes) and its tags are WordPress tags. The front page shows the newest post and the five before it; each post has a series badge, highlighted Code blocks with copy buttons, heading links, an "On this page" outline that follows your reading, a reading progress bar, a series box and links to the older and newer posts; the Archive, Tags and Search pages list, filter and search every post. Series, Projects, About (with a now section) and Subscribe pages complete the site, on graph paper with a red margin line and a yellow highlighter, in light and dark mode. This guide covers version 1.0.0.

Live demo: freeframe.dev/templates/notebook

How the WordPress version is organised#

Part Where Edit it in
Pages (Home, Archive, Series, Tags, Projects, About, Search, Subscribe) Real pages, created from page patterns by Notebook setup Pages > Edit
Posts WordPress posts; series are categories, tags are tags Posts
Post layout templates/single.html → patterns/post.php Appearance > Editor > Templates > Single Posts
Lists of posts Query Loops (patterns/home-latest.php, patterns/posts.php, both using patterns/post-row.php) Editor, or the page
Archive, tags, search, series box, older/newer Built in the browser by assets/js/main.js from a post index printed by inc/content.php Your posts (nothing to edit)
Header and footer patterns/header.php, patterns/footer.php Appearance > Editor > Patterns
Colours, fonts, sizes theme.json Appearance > Editor > Styles

Sections are block patterns in the "Notebook" category, so each one can also be inserted into any page.

Quick start#

  1. Appearance > Themes > Add New Theme > Upload Theme: upload the zip from freeframe.dev and activate Freeframe Notebook (WordPress 7.1 or later, PHP 7.4 or later).

  2. Appearance > Notebook setup > Create the demo content. It creates:

    • the categories Postgres from the inside, A build system in 200 lines, CSS for backend people (series) and Notes;
    • fourteen sample posts with their tags, dates, series parts and reading times (one with full text, the others short placeholders);
    • the pages Home, Archive, Series, Tags, Projects, About, Search and Subscribe, Home as the front page, and the Main and Mobile menus.

    Anything that already exists (same slug) is left alone, so running it twice is harmless.

  3. Delete the sample posts you don't want, and write your own.

To try it without a server: pnpm install && pnpm dev in the theme folder (Node.js 24 and pnpm) starts WordPress in WordPress Playground at http://127.0.0.1:9400 with the theme and the demo content.

Pages and templates#

Page or template Pattern(s) Notable features
Home (/) home-intro, home-latest, home-series, home-tags, newsletter Newest post as a card, five latest posts, series cards, tags, newsletter form
A post (templates/single.html) post Series badge, outline, progress, Code blocks with copy buttons, series box, older/newer posts
Archive (/archive/) archive Every post grouped by year with jump links
Tags (/tags/) tags Tag buttons with counts, ?tag= in the address
Series (/series/) series-intro, series-postgres, series-build, series-css, series-next Each series with its parts in order
Search (/search/) search Instant search with highlighted matches, ?q= in the address
About (/about/) about-intro, about-now, about-uses, about-contact Portrait, now, uses, contact
Projects (/projects/) projects, talks Project cards and talks
Subscribe (/subscribe/) subscribe Email form, RSS address with a copy button
404 (templates/404.html) 404 Search box and links back
Blog, archives, WordPress search (index.html, archive.html, search.html) posts Lists of posts in the same rows as the home page
Page with title (custom template) none For simple text pages you add

Project structure#

freeframe-notebook/
  style.css             theme header (name, version, requirements)
  theme.json            colours (light and dark), fonts, font sizes, layout widths
  functions.php         loads inc/
  inc/setup.php         styles, script, dark-mode script, head tags, script strings, pattern categories
  inc/content.php       post URLs, post fields, block bindings, term links, the post index
  inc/site-setup.php    Appearance > Notebook setup and the demo content
  inc/block-styles.php  button styles (Outline, Series badge)
  inc/icons.php         the icon collection for the Icon block (assets/icons)
  templates/            page, single, index, archive, search, 404, page-with-title
  parts/                header and footer (each loads its pattern)
  patterns/             sections, page patterns, the post layout and lists of posts
  assets/css/           theme.css and editor.css (compiled, don't edit)
  assets/js/main.js     all interactive features
  assets/fonts/         Martian Mono, Atkinson Hyperlegible Next and Mono (woff2 + licenses)
  assets/img/           logo, favicon, illustrations, portrait, social image
  assets/icons/         icons used by Icon blocks
  src/theme.css         Tailwind CSS source of assets/css/theme.css
  languages/            translation template (freeframe-notebook.pot)
  blueprint.json        WordPress Playground setup used by pnpm dev

Writing posts#

Posts > Add New:

  • Title and Excerpt (Post sidebar > Excerpt): the summary shown under the title and in every list. Lists and search use it, so write one for every post.
  • Category: the series, or Notes for a post that stands alone. A new category is a new series (every category except Notes and the default "Uncategorized" counts as one); give it a description.
  • Tags: lower-case tags read best, since they're shown as #postgres.
  • Text: Heading 2 and Heading 3 blocks become the outline and get a # link. Use Code blocks for code; Notebook gives each one a language label and a copy button.

Posts left in "Uncategorized" are not listed by the home page, the archive, tags or search; give them a series or Notes.

Code blocks#

Add the class lang-sql (or lang-bash, lang-js, lang-rust, lang-css...) in the Code block's Advanced > Additional CSS class(es) to label its language; without one it reads "Output". The demo's example post keeps hand-made highlighting as <span class="tok-k"> (keywords), tok-s (strings), tok-n (numbers), tok-c (comments) and tok-f (functions) inside its Code blocks (edit them as HTML); plain Code blocks show unhighlighted code in the code font.

Post fields#

Under Preferences > Panels > Custom Fields:

Field Meaning
reading_time Minutes shown in lists, e.g. 6. Empty: counted from the text at 230 words a minute
series_part The part number in the series, e.g. 2. Empty: counted from the dates of the series' posts

On the post itself the reading time is counted from the text in the browser.

Customising#

Brand and colours#

Appearance > Editor > Styles > Colors edits the palette in theme.json. Every colour has a light and a dark version: Paper / Paper (dark) is the background, Ink the text, Muted secondary text, Line borders, Pen the red pen (margin lines, labels, series badges, active outline entry), Pen soft the series badge background and Marker the highlighter (link underlines, the current menu item, search matches). src/theme.css maps them to the variables the design uses (--paper, --ink, --pen, --marker...), and also holds the code colours (--code-bg, --tok-k...) and the graph-paper --grid for each theme.

Typography#

Martian Mono (headings and labels, condensed with font-stretch: 87.5%), Atkinson Hyperlegible Next (text) and Atkinson Hyperlegible Mono (code) are bundled in assets/fonts/ and declared in theme.json, so no font is loaded from Google. Change them in Styles > Typography or in theme.json.

Dark mode#

The theme switch in the header stores "dark" or "light" under notebook-theme in the browser; a small script printed first in <head> (inc/setup.php) applies it before the page paints, otherwise the visitor's system setting is used. To turn dark mode off, remove the switch from the header pattern and add add_filter( 'freeframe_notebook_dark_mode', '__return_false' ); to a child theme.

Appearance > Notebook setup creates the Main menu and Mobile menu (Archive, Series, Tags, Projects, About). Edit them in Appearance > Editor > Navigation. The search link (with its / shortcut), the theme switch and the menu button are small Custom HTML blocks in the header pattern; the footer columns are lists you can edit in Appearance > Editor > Patterns > Footer.

Content in pages#

Every page is ordinary blocks: edit the text, links, buttons and pictures in Pages > Edit. The series page's parts, the home page's series cards and tags and the projects are written in the pages (update them when you publish); lists of posts update themselves.

Classes the script reads#

Blocks can't keep data- attributes, so patterns write them as classes that assets/js/main.js turns back into attributes before anything else runs: ffd-<name>--<value> becomes data-<name>="<value>", ffa-<name>--<value> becomes an attribute such as aria-labelledby, and ff-hidden becomes hidden. The post content has ffd-post-body (headings, code blocks and reading time), the reading time paragraph ffd-reading-time and the progress bar ffd-progress. Keep these classes when you edit those blocks.

Features#

Lists of posts#

The home page's newest post and latest posts, the blog page and the date archives are Query Loops using patterns/post-row.php: date, title, excerpt, the series badge and the reading time (both from the freeframe-notebook/post block bindings source in inc/content.php) and the tags. Tag links open the Tags page (/tags/?tag=postgres) and series links the Series page (/series/#<slug>).

The post index#

On every page inc/content.php prints window.NOTEBOOK_POSTS (slug, title, excerpt, date, tags, series, part, minutes and address of every post outside the default category, newest first), window.NOTEBOOK_SERIES and window.NOTEBOOK_LINKS before the script, and on a post window.NOTEBOOK_CURRENT. Filter freeframe_notebook_index to change it.

The post page#

patterns/post.php: the series badge (a Button block with the "Series badge" style), title, excerpt, date, reading time, a Copy link button and the tags, then the series box, the outline, the text, the older/newer links and a note about replies. In the text, every Code block gets a frame with its language and a Copy button (it reads "Copied" for two seconds and a message is announced), every Heading 2 and 3 gets an id and a # link that also copies the section's address, and the outline (a sidebar on wide screens, "On this page" on phones) highlights the section you are reading. The bar at the top shows how far you have read.

The Archive page groups every post by year with links to each year. The Tags page shows a button for each tag (most used first), lists the posts of the chosen one, announces the count and keeps the tag in the address; ?tag= is matched in any case and a tag that doesn't exist is ignored with a short note. The Search page searches titles, excerpts, tags and series as you type (every word must match), marks the matches and keeps the query in ?q=. Press / on any page to go to search. The theme stops WordPress from reading /tags/?tag= as a tag archive (inc/content.php).

Scroll reveals, mobile menu, back to top#

Elements with the class reveal fade in as they scroll into view (not with reduced motion). The mobile menu opens below the header, closes with Escape and returns focus to its button. A back-to-top button appears after about one screen.

Forms#

Form Pattern Fields
Newsletter newsletter (home) email
Email subscription subscribe first name (optional), email, how often, series announcements
Search search, 404 query (a plain GET form to the Search page)

Forms with data-validate are checked in the browser: errors appear under the field, fields get aria-invalid, and the first invalid field is focused. When everything is valid the form resets and its [data-form-success] message is shown and focused.

Nothing is sent anywhere: they are demo forms until you connect them, with a newsletter plugin (replace the form's Custom HTML block with the plugin's block; add the classes input, label and btn to its fields and button) or a service such as Buttondown or Formspree: add its endpoint as the form's action and method="post", and in assets/js/main.js, at the comment "Send the data to your form endpoint here", send it with fetch(form.action, { method: 'POST', body: new FormData(form), headers: { Accept: 'application/json' } }) (in a child theme's copy of the script). Also rewrite the thank-you texts, which say that nothing was sent.

Saved data (localStorage)#

Key Value Purpose
notebook-theme "dark" or "light" The reader's theme choice; any other value is ignored and the system setting is used

SEO and sharing#

Page titles come from WordPress: the page or post title, an em dash and the site name; the front page shows the site name and tagline. The theme doesn't output meta descriptions or Open Graph tags; use an SEO plugin (Yoast SEO, Rank Math, The SEO Framework) and give it assets/img/og-image.png as the default social image. WordPress generates a sitemap at /wp-sitemap.xml and an RSS feed at /feed/; put that address in the Subscribe page's feed box.

Accessibility#

Built in: a skip link (added by WordPress, styled by the theme), landmarks (header, labelled navigation, main, labelled sections and asides, footer), one h1 per page, visible focus outlines, the theme switch and tag buttons with aria-pressed, the mobile menu button with aria-expanded and Escape, live regions for counts, copy messages and form messages, focusable and labelled code blocks, heading links named after their section, aria-current on the current menu item, outline entry and series part, and reduced-motion support.

When you edit: keep one Heading 1 per page and use Heading 2 and 3 for a post's sections, give pictures alt text, and check the contrast of any colour you change in both themes (WCAG AA).

Building and deploying#

You only need this if you change the theme's files. assets/css/theme.css is compiled with Tailwind CSS from src/theme.css and the classes used in patterns/, parts/, templates/, inc/ and assets/js/:

sh
pnpm install
pnpm build   # compile assets/css/theme.css and assets/css/editor.css
pnpm watch   # recompile assets/css/theme.css on every change (run pnpm build before you commit)
pnpm dev     # build, then run WordPress Playground with the theme at http://127.0.0.1:9400

To install the theme, zip the folder (without node_modules/) and upload it in Appearance > Themes.

Translations#

Every text in the patterns, the setup and the script is translatable with the text domain freeframe-notebook; languages/freeframe-notebook.pot is the template. Put freeframe-notebook-<locale>.mo in the theme's languages/ folder or in wp-content/languages/themes/. The script's strings are translated in inc/setup.php (freeframe_notebook_script_strings()).

Child themes#

Create wp-content/themes/notebook-child/style.css with Template: freeframe-notebook, then override templates or patterns by copying them into the child theme. Filters: freeframe_notebook_dark_mode, freeframe_notebook_demo_posts (before the setup creates posts) and freeframe_notebook_index (the post index).

Troubleshooting#

Problem Fix
A post is missing from the home page, archive, tags or search It is in "Uncategorized"; give it a series or Notes
The series badge shows on a post that isn't in a series Its category is a series; move it to Notes
A Code block has no language label Add the class lang-<name> in Advanced > Additional CSS class(es)
/tags/?tag=… shows every post The tag has no posts, or the Tags page's slug isn't tags
Tag links go to WordPress tag archives The theme sends them to the page with the slug tags; create it with Notebook setup
New classes have no effect Run pnpm build so Tailwind generates them

License#

MIT (GPL-compatible), see LICENSE.txt in the download. The illustrations, portrait, logo and icons are original artwork included under the same license. The fonts are under the SIL Open Font License 1.1 (see assets/fonts/*/OFL.txt).

Search documentation

Search the guides and every template's documentation