Overview#
Folio is a bold, dark portfolio for product designers, developers and small studios, built as a
WordPress block theme. The demo belongs to Mira Okafor, a fictional independent product designer
in Lisbon, with six sample projects and one complete case study. The theme has a home page with an
oversized animated headline, a work page with a category filter, a case study page (facts,
challenge, process, screens and results), an about page with a portrait, timeline, skills and
awards, a contact page with a copy-email button and a project inquiry form, and a 404 page. It is a
dark-only design with an orange accent. It is version 1.0.1 (see template.json and style.css),
works with WordPress 7.1 and PHP 7.4 or newer, and is the WordPress version of the HTML template of
the same name. See it live at freeframe.dev/templates/folio.
Everything you see is made of core WordPress blocks arranged in block patterns, so you edit text,
links, buttons, lists and images in the block editor without touching code. Colours, the two fonts
and font sizes are design tokens in theme.json: change them in Appearance > Editor > Styles and
the whole site follows.
Quick start#
Requirements: WordPress 7.1 or newer and PHP 7.4 or newer. No plugins are needed.
- Download
folio.zipfrom freeframe.dev/templates/folio (the WordPress tab). - In your WordPress admin go to Appearance > Themes > Add New Theme > Upload Theme, choose the zip and click Install Now, then Activate. The theme is listed as Freeframe Folio.
- Go to Appearance > Folio setup and click Create the demo pages. This creates the Home, Work, Case study, About and Contact pages from the theme's page patterns, sets Home as the front page (Settings > Reading), gives the Contact page the Page with simple footer template and creates the two header menus. Pages that already exist with the same slug are left alone, so it is safe to click it again.
- Set your name in Settings > General > Site Title (it is shown in the header and in page titles), then visit your site. Edit any page in Pages, and the header, footers and templates in Appearance > Editor.
You can also try the theme without installing anything: the Try it in WordPress button on freeframe.dev opens it in WordPress Playground (WordPress running in your browser) with the demo pages already set up.
To work on the theme's files locally you need Node.js 24 and pnpm; WordPress itself runs in WordPress Playground, so there is no PHP, web server or database to install:
pnpm install
pnpm dev # builds the CSS, then starts WordPress with the theme at http://127.0.0.1:9400pnpm dev signs you in as admin (password password), sets the site title to "Mira Okafor",
switches on pretty permalinks and creates the demo pages (blueprint.json). The site is rebuilt
from scratch every time you start it; your edits to theme files are live, edits made in the
WordPress admin are not kept.
Pages#
Each page is a page pattern (in Patterns > Folio pages) made of section patterns. Every section is also offered on its own in the block inserter under Patterns > Folio.
| Page | Slug | Page pattern | Sections (pattern files in patterns/) |
|---|---|---|---|
| Home | / |
page-home.php |
home-hero (oversized headline, studio clock), selected-work (staggered grid of four projects), services, clients (client name marquee), testimonial |
| Work | /work/ |
page-work.php |
work-hero, work-grid (category filter and six project cards) |
| Case study | /project/ |
page-project.php |
case-study (facts, overview, challenge, process, screens, results), next-project |
| About | /about/ |
page-about.php |
about-hero, about-intro (portrait and bio), about-numbers, experience (timeline), skills, awards |
| Contact | /contact/ |
page-contact.php |
contact-hero (email and copy button), contact-form (availability, "Good to know", inquiry form) |
| 404 | any missing URL | templates/404.html |
404 |
Every page except Contact ends with the default footer and its "Let’s make it real." call to action; Contact and 404 use the footer without it (see Navigation, header and footer).
Posts use templates/single.html; the blog, archives and search results use templates/index.html,
templates/archive.html and templates/search.html.
Project structure#
freeframe-folio/
style.css theme header (name, version, requirements, license); no styles
theme.json design tokens: palette, fonts, font sizes, spacing, layout widths, button;
the custom templates and the template parts
functions.php loads the files in inc/
inc/setup.php stylesheet and script, the script's translations, editor styles, head
meta (theme colour, favicon), pattern categories
inc/icons.php registers assets/icons/*.svg for the Icon block
inc/icon-labels.php the icons' names shown in the editor
inc/site-setup.php Appearance > Folio setup: creates the demo pages and the two menus
templates/ page.html (pages), page-with-title.html, page-simple-footer.html,
single.html, index.html, archive.html, search.html, 404.html
parts/ header.html, footer.html and footer-simple.html (they show the header,
footer and footer-simple patterns)
patterns/ every section and page (PHP files with block markup); footer-columns.php
is shared by both footers
assets/css/theme.css the compiled stylesheet (do not edit: built from src/)
assets/css/editor.css the same for the block editor (generated by src/editor-css.mjs)
assets/js/main.js the site's JavaScript
assets/icons/ SVG icons for the Icon block (arrow-right, arrow-up-right, quote)
assets/img/ project artwork, case study screens, portrait, favicon, social image
assets/fonts/ Inter Tight and Instrument Serif files and their licenses (OFL.txt)
src/theme.css the Tailwind CSS source of assets/css/theme.css
src/icons.css icons drawn before or after text with CSS (generated)
src/editor-css.mjs writes assets/css/editor.css after each build
languages/ freeframe-folio.pot, the template for translations
blueprint.json WordPress Playground setup used by `pnpm dev`
screenshot.png the picture shown in Appearance > Themes
package.json the CSS build and the local WordPress (Node.js tooling)
readme.txt, README.md, DOCS.md, CHANGELOG.md, LICENSE.txtCustomising#
Brand and colours#
The colours are in theme.json (settings.color.palette) and in Appearance > Editor > Styles >
Colors > Palette. The stylesheet reads them through Tailwind tokens (in src/theme.css), so a
change in Styles recolours every block that uses the token:
| Colour (slug) | Tailwind token | Used for |
|---|---|---|
Background (base) |
bg |
page background, button text on the accent |
Ink (contrast) |
ink |
main text, the hover colour of primary buttons |
Surface (surface) |
surface |
project image backgrounds, process cards, the case study's challenge band |
Raised (raised) |
raised |
the back-to-top button, code in posts |
Line (line) |
line |
borders and dividers |
Muted (muted) |
muted |
secondary text, menu links, eyebrows |
Accent (accent) |
accent |
primary buttons, the italic headline words, focus rings, the cursor badge |
Accent soft (accent-soft) |
accent-soft |
a lighter accent for your own blocks |
To switch the accent to a green, change Accent (and Accent soft) in Styles, or in theme.json:
{ "slug": "accent", "name": "Accent", "color": "#2fbf71" }Folio has no light mode: it is designed for a dark background with light text. If you change
Background, also change the browser UI colour printed by freeframe_folio_head_meta() in
inc/setup.php (<meta name="theme-color" content="#0b0b0c">); the same function declares
color-scheme: dark, which gives form controls and scrollbars their dark look.
A few colours are written in the stylesheet itself rather than the palette: the form's error red
(.field-error, .field[aria-invalid="true"]), the placeholder grey (.field::placeholder) and
the green availability dot (.status-dot), all in src/theme.css.
Typography#
Two fonts are bundled in assets/fonts/ and declared in theme.json
(settings.typography.fontFamilies), so no request goes to Google:
| Font | Slug | Used for |
|---|---|---|
| Inter Tight (variable, weights 300 to 800) | inter-tight |
all text and headings (the site's Text font) |
| Instrument Serif (regular and italic) | instrument-serif |
the italic accent words (serif-accent), service and process numbers, the testimonial |
| Monospace (system fonts) | mono |
code in posts |
To use another font, install it in Appearance > Editor > Styles > Typography > Fonts (the Font
Library) and select it for Text, or replace the fontFace entries in theme.json. The
theme's utility classes use the fonts through their slugs (--font-sans is
--wp--preset--font-family--inter-tight, --font-serif is
--wp--preset--font-family--instrument-serif in src/theme.css), so rename a slug only together
with src/theme.css.
Font sizes are the presets in theme.json (xs 0.75rem to 9xl 8rem, the Tailwind scale). The
stylesheet uses them too: changing Large in Styles changes every text-lg in the design. The
oversized headlines are fluid sizes written as classes on the Heading blocks (for example
text-[clamp(3.1rem,11.5vw,11rem)] on the home page): change them there.
Content#
Edit pages in Pages: click any text to change it, select a button to change its link, select an image and use Replace. Sections are groups; open List View (the stacked lines icon at the top left) to select a section, move it, duplicate it or delete it. To add a section from another page, use the block inserter (+) > Patterns > Folio.
The demo's name, email address (hello@example.com), phone number and city appear in several
places: the header (mobile menu), both footers (patterns/footer-columns.php), the home hero, the
about page and the contact page. Search each page for them when you rebrand.
Some content is in Custom HTML blocks, because blocks can't express it: edit those in the block's HTML view.
| Pattern | Custom HTML | What it is |
|---|---|---|
header |
the menu button | the button that opens the mobile menu, driven by assets/js/main.js |
work-grid |
the filter bar | the category buttons and the project count; see Work filter |
contact-hero |
the Copy email button | its data-copy attribute holds the address it copies |
contact-form |
the form and its thank-you message | see Forms |
A few blocks carry a class (Block settings > Advanced > Additional CSS class(es)) that the script or stylesheet reads:
revealfades a block in as it scrolls into view. A delay is a class such as[--d:120ms]; the stylesheet contains0ms,80ms,90ms,100ms,120ms,160ms,180ms,200ms,240ms,270msand300ms(other values needpnpm build).reveal-lineis used inside the big headlines: each line is<span class="reveal-line"><span>…</span></span>and slides up out of a mask. To add or change a line, open the heading's Edit as HTML (block toolbar > Options).ff-category-<name>on a project card puts it in a filter category; see Work filter.ff-cursor-view,ff-cursor-nextandff-cursor-say-hishow the cursor badge with "View", "Next" or "Say hi"; see Cursor badge.ff-local-timeon a<span>inside text shows the studio's local time; see Studio clock.link-cardon a group makes the whole group clickable: the first link inside it (the project title) stretches over the group. The project cards and the next-project card use it.serif-accent(on a<span>inside a heading) sets the italic Instrument Serif accent;text-accentcolours it orange.
Projects. A project card is a Group (article, class project-card-wrap) holding a Group with
project-card group ff-cursor-view link-card: the image in a project-media group, the title
(a Heading 3 whose link is the card's link), a meta line ("Mobile app · 2026"), on the Work page a
short description, and the arrow icon. To add a project on the Work page, select a card's outer
Group in List View, duplicate it, then change the image, title, link, meta line, description and
ff-category- classes. The home page's Selected work is a hand-picked copy of four cards in two
columns (the right-hand column is offset with md:pt-32). Update the counts by hand: the number in
the Selected work heading ("(04)") and the filter's counts (see Work filter).
Rolling dates. "Booking projects from November 2026" in the contact form pattern is <span data-months-ahead="n">: assets/js/main.js shows the month n months from now and its year, so
the line never goes stale. The month written in the page is shown without JavaScript. For a fixed date, replace the span
with plain text.
Case studies#
The Case study page (/project/) is one complete static case study of Tidal Banking, and every
project card, on the home and Work pages, links to /project/. To publish a case study per
project:
- Duplicate the Case study page (in Pages, or create a page and insert Patterns > Folio
pages > Case study page, or the Case study section from Patterns > Folio), give it its
own slug (for example
/verde-grocery/) and replace its text and images. - Change the link on that project's title on the Work page and on the home page.
- Point the Next project card at the next case study: its link is on the "Next project" eyebrow; change the name, meta line and image too.
The facts (Client, Role, Timeline, Team, Deliverables) are small groups of two paragraphs in a grid: duplicate or delete one in List View. The results are three large numbers with a caption.
Images and artwork#
- Project artwork (
assets/img/project-*.svg, 16:10, 1600 × 1000) and the case study screens (tidal-screens.svg,tidal-system.svg, 16:9) are SVG illustrations shown with Image blocks. Replace them with screenshots from the Media Library at the same aspect ratio (the frame crops to it withobject-fit: cover). Keep the classcontentson the Image block (Advanced): it lets the picture fill its rounded frame. Give each image alt text that describes the project. - Portrait (
assets/img/portrait.svg, on the About page): replace it with a photo in the Image block; it fills the column's width with rounded corners. - Logo: the header shows a round "m" mark (a Paragraph block) next to the Site Title block. Change the letter in Appearance > Editor > Patterns > Header; the name comes from Settings > General > Site Title.
- Icons: the card arrows and the testimonial's quote mark are Icon blocks. Select one and pick
another icon from the Folio collection (the theme's icons) or WordPress's own. Arrows inside
buttons are drawn by CSS: the classes
icon-after-arrow-right icon-size-14on a Button block add one; the available names are insrc/icons.css. - Client names in the marquee are list items in a List block; see Client marquee.
- Social image:
assets/img/og-image.png(1200 × 630, sourceog-image.svg). WordPress doesn't output Open Graph tags by itself; see SEO and sharing. - Favicon:
assets/img/favicon.svgis used until you set a Site Icon in Settings > General.
WordPress doesn't allow SVG uploads by default; upload your replacements as JPG, PNG or WebP.
Navigation, header and footer#
The header is a template part (parts/header.html) showing the header pattern: the logo, the
desktop menu, a "Start a project" button and the full-screen mobile menu. Edit it in
Appearance > Editor > Patterns > Header.
It has two Navigation blocks with two different menus, both created by Folio setup:
| Menu | Links | Shown |
|---|---|---|
| Main menu | Work, About, Contact | in the header from 768 px wide |
| Mobile menu | Home, Work, About, Contact | in the full-screen menu below 768 px |
Edit them in Appearance > Editor > Navigation. To add a page to the site's navigation, add it
to both menus. The mobile menu numbers its links (01, 02...) with a CSS counter
(.site-nav-mobile in src/theme.css), so the numbers follow the order of the links. The header
pattern points each Navigation block at its menu by id (freeframe_folio_menu_ref() in
inc/site-setup.php reads the ids saved in the freeframe_folio_menus option). If a menu doesn't
exist, the Navigation block shows the most recently created menu; select the block and choose the
menu in the block settings.
There are two footers, both template parts showing a pattern:
| Template part | Pattern | Content | Used by |
|---|---|---|---|
Footer (parts/footer.html) |
footer |
the oversized "Let’s make it real." call to action, then the columns | pages, posts, the blog, archives and search |
Footer without call to action (parts/footer-simple.html) |
footer-simple |
the columns only | the Page with simple footer template (the Contact page) and templates/404.html |
The columns (contact, studio address, "Elsewhere" links, local time, copyright and a "Back to
top" link) are one pattern, patterns/footer-columns.php, nested in both footers. In the Site
Editor each footer is saved separately, so once you edit the columns in one footer, make the same
change in the other. The "Elsewhere" links are a List block: click a link to change it (they point
at # in the demo), press Enter to add one.
To give any page the footer without the call to action, open the page, then in the page settings choose Template > Page with simple footer.
The copyright line ("© 2026 Mira Okafor. All rights reserved.") prints the current year when the footer comes from the theme's files. After you edit and save a footer in the Site Editor, the year is stored as text; update it there each year.
Features#
Header and mobile menu#
The header is fixed at the top and gains a blurred background and a bottom border once the page
scrolls (is-scrolled, set by assets/js/main.js). Below 768 px the menu button (.menu-toggle)
opens the full-screen #mobile-menu: it updates aria-expanded and its label ("Open menu" /
"Close menu"), stops the page from scrolling behind it and moves focus to the first link. It
closes on Escape (returning focus to the button), when you click the button again and when the
window grows past 768 px. The current page's link is highlighted through aria-current="page",
which the Navigation block sets.
Scroll reveal and headlines#
Blocks with the reveal class fade and rise when they enter the viewport, and the lines of the big
headlines (reveal-line) slide up one after another (IntersectionObserver in
assets/js/main.js). The [--d:…] classes stagger them. Content is only hidden once JavaScript
runs (the js class that freeframe_folio_theme_script() adds to <html> first in <head>), so
the page reads fine without it. With reduced motion enabled, everything is shown immediately. In
the editor everything is always visible.
Work filter#
The bar at the top of the Work page (work-grid pattern, a Custom HTML block) is a
role="group" with data-filter-group. Each button has data-filter and aria-pressed, and a
count in <sup>; data-filter="all" shows everything. It sticks below the header as you scroll.
<button type="button" class="chip" data-filter="brand" aria-pressed="false">Brand<sup>2</sup></button>The project cards are Group blocks with one class per category, for example
ff-category-web ff-category-brand; the script turns them into data-category="web brand".
Clicking a button:
- shows the cards whose categories contain the button's value and hides the rest (with a fade, or instantly with reduced motion);
- updates the count in
[data-filter-count], zero-padded ("03 projects"); - writes the filter to the address, so
/work/#brandopens with Brand selected; All removes it.
To add a category, add a button with a new data-filter value (lowercase, no spaces) in the Custom
HTML block, and add ff-category-<value> to the cards in that category (Advanced > Additional
CSS class(es) on the card's outer Group). The counts in the <sup> elements and the initial
"06 projects" are text: update them when you add or remove projects.
Cursor badge#
On devices with a mouse or trackpad, a round accent badge follows the pointer and grows over the
project cards ("View"), the next-project card ("Next") and the footer's call to action ("Say hi").
An element gets it from a class: ff-cursor-view, ff-cursor-next or ff-cursor-say-hi (the
script turns it into data-cursor). Another word works too: ff-cursor-read-more shows "Read
more" (untranslated). The badge is created by the "Cursor follower" part of assets/js/main.js,
is aria-hidden, and is not created at all on touch screens or with reduced motion. Remove the
classes to turn it off for an element.
Studio clock#
The time next to "Lisbon" in the home hero, the footer and the contact page is a
<span class="ff-local-time"> inside the text, updated every 30 seconds. The time zone is set in
assets/js/main.js:
var fmt = new Intl.DateTimeFormat('en-GB', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Lisbon' });Change timeZone to any IANA zone (for example America/New_York) in a child theme (see
Child themes) and change the city in the text next to each clock. The 12:00 in
the markup is shown until the script runs.
Client marquee#
The scrolling client names on the home page (clients pattern) are a List block (marquee-track)
that moves left forever and pauses on hover; one loop takes 38 seconds (--animate-marquee in
src/theme.css). The list holds the names twice so the loop is seamless: when you change a name,
change both copies, in the same order. Each name has its own type style through classes on the
list item. With reduced motion the strip doesn't move.
Copy email button#
The button next to the email address on the Contact page copies the value of its data-copy
attribute to the clipboard and changes its label ([data-copy-label]) to "Copied" for a moment
(or "Press Ctrl+C" if the browser blocks clipboard access). When you change the email address,
change the link and the button's data-copy in its Custom HTML block.
Good to know#
The questions under "Good to know" on the Contact page are core Details blocks: click the summary
to edit the question, and the paragraph inside for the answer; duplicate one to add a question.
They open with the keyboard and screen readers like any <details> element. The "+" that turns
into a "×" is a <span> at the end of the summary text.
Back to top#
A button appears after scrolling about one screen down; it scrolls to the top (instantly with
reduced motion) and moves keyboard focus to the main content. It hides while the footer's own
"Back to top ↑" link is on screen. Add data-back-to-top-avoid to anything you pin to the bottom
of the screen (a cookie bar, a chat launcher) and the button sits above it. It is created by
assets/js/main.js; to remove it, delete the "Back to top" part of that file and the
.back-to-top styles in src/theme.css, then run pnpm build.
Buttons#
The Button block's default style is the primary button (accent background, Ink on hover);
WordPress's own Outline style (block settings > Styles) is restyled as the outlined button.
Folio registers no other block styles. Add the classes icon-after-arrow-right icon-size-14 to a
Button block for the arrow that slides on hover.
Forms#
The project inquiry form on the Contact page (a Custom HTML block in the contact-form pattern,
marked data-validate) checks what visitors type in the browser (assets/js/main.js):
| Field | name |
Rule |
|---|---|---|
| I’m interested in | type (checkboxes) |
optional |
| Your name | name |
at least 2 characters |
email |
a valid email format | |
| Company | company |
optional |
| Budget | budget |
a range must be chosen |
| Ideal start | timeline |
a timeline must be chosen |
| Project details | message |
at least 20 characters |
Errors appear under each field (#<name>-error, announced with aria-live), fields get
aria-invalid, and focus moves to the first invalid one. When everything is valid, the form is
replaced by its thank-you message (#form-success, a second Custom HTML block), which greets the
sender by first name. Nothing is sent anywhere: it is a demo form until you connect it. When
you do, also rewrite the thank-you message, which says that nothing was sent.
Option 1: a form plugin#
The usual way on WordPress. Install a form plugin (WPForms, Gravity Forms, Fluent Forms, Contact
Form 7, Jetpack Forms...), build the form there, then on the Contact page delete the two Custom
HTML blocks of the form and its thank-you message and insert the plugin's block or shortcode in
their place. The plugin sends the messages and stores them. To make its fields look like the
theme's, add the theme's classes where the plugin allows custom classes: field on fields,
field-label on labels, btn btn-primary on the submit button.
Option 2: a form service#
Keep the theme's form and send it to a service such as Formspree:
-
Create a form on the service and copy its endpoint URL.
-
Edit the Contact page, open the form's Custom HTML block and put the endpoint in the form tag's
action(it is#in the demo):html<form class="space-y-10 reveal" action="https://formspree.io/f/your-id" method="post" novalidate="" data-validate=""> -
In
assets/js/main.js, find// Demo only: nothing is sent.in the form's submit handler and replace the lines from there to the end of the handler with a request that only shows the thank-you message when the service accepts it:jsvar success = document.getElementById('form-success'); var button = form.querySelector('[type="submit"]'); button.disabled = true; fetch(form.action, { method: 'POST', body: new FormData(form), headers: { Accept: 'application/json' } }) .then(function (res) { if (!res.ok) throw new Error('Request failed'); form.hidden = true; if (success) { var nameEl = success.querySelector('[data-name]'); if (nameEl) nameEl.textContent = form.elements.name.value.trim().split(' ')[0]; success.hidden = false; success.focus(); } }) .catch(function () { button.disabled = false; alert('Sorry, the message could not be sent.'); });Make this change in a child theme (see Child themes), or it will be overwritten when you update the theme.
SEO and sharing#
Page titles come from WordPress: the page title, an em dash and the site name (for example
"Case study — Mira Okafor"); the home page shows the site name and tagline (Settings >
General). The theme doesn't output meta descriptions or Open Graph tags; use an SEO plugin such as
Yoast SEO, Rank Math or The SEO Framework, and give it assets/img/og-image.png (or your own
1200 × 630 image) as the default social image. WordPress already generates a sitemap at
/wp-sitemap.xml.
Accessibility#
Built in: a skip link to the main content (added by WordPress, styled by the theme), landmarks
(header, navigation with labels, main, footer), one h1 per page, visible accent focus rings, the
mobile menu button with aria-expanded and Escape to close, filter buttons with aria-pressed in a
labelled group, a labelled "Next project" navigation, keyboard-friendly Details blocks, form errors
linked with aria-describedby and announced with aria-live, aria-invalid on wrong fields, alt
text on the project images and portrait, and reduced-motion support for every animation (reveals,
marquee, filter, cursor badge, back to top). The cursor badge is decorative (aria-hidden).
When you edit: keep one Heading 1 per page, give images alt text (or mark them decorative), keep
link text meaningful, label any field you add to the form (<label for="...">), and check the
contrast of any colour you change against the dark background (WCAG AA).
Building and deploying#
You only need this if you change the theme's files. The stylesheet 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/:
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)Run pnpm build after adding a utility class to a pattern or template, otherwise the class has no
style. Classes you type in the editor's Additional CSS class(es) field are only styled if the
stylesheet already contains them.
To deploy, zip the theme folder (named freeframe-folio, without node_modules/) and upload it
in Appearance > Themes, or copy the folder to wp-content/themes/ on your server.
Translations#
The theme is translation-ready with the text domain freeframe-folio. All text in patterns
(including the Custom HTML blocks), templates and PHP goes through WordPress's translation
functions; the script's strings (menu labels, "Copied", the form errors, the cursor badge labels,
"Back to top") are translated in PHP (freeframe_folio_script_strings() in inc/setup.php,
printed as window.freeframeFolioL10n). Create a translation with Loco Translate, or with WP-CLI
and Poedit:
wp i18n make-pot wp-content/themes/freeframe-folio wp-content/themes/freeframe-folio/languages/freeframe-folio.potSave freeframe-folio-de_DE.mo (for example) in wp-content/languages/themes/ or in the theme's
languages/ folder. Page content created by Folio setup is copied into your pages in the site's
language at that moment; translate it in the editor like any other content. The studio clock uses
the 24-hour en-GB format whatever the language (see Studio clock).
Child themes#
Use a child theme for changes you want to keep when Folio is updated. Create
wp-content/themes/folio-child/style.css:
/*
Theme Name: Folio Child
Template: freeframe-folio
*/Templates, parts, patterns and theme.json in the child theme override the parent's files of the
same name. The parent's stylesheet only contains the classes the parent uses: if your child theme's
patterns use new utility classes, compile a stylesheet for the child (copy package.json and
src/theme.css, point the @source lines at the child's folders) and enqueue it.
To change the script (the clock's time zone, sending the form), copy assets/js/main.js into the
child theme and load it in place of the parent's from the child's functions.php. Keeping the
script's handle keeps its translations:
add_action( 'wp_enqueue_scripts', function () {
wp_scripts()->registered['freeframe-folio']->src = get_stylesheet_directory_uri() . '/assets/js/main.js';
}, 30 );Troubleshooting#
- The pages look unstyled or new classes do nothing. The stylesheet only contains classes it
found when it was built. Run
pnpm buildafter editing patterns, then reload. - "This block contains unexpected or invalid content". A pattern or page was edited by hand with markup that doesn't match the block. Click Attempt recovery, or re-insert the section from Patterns > Folio.
- The header shows the same menu twice, or an old menu. A Navigation block without a chosen menu shows the most recent one. Select the desktop one and choose "Main menu", and the one in the mobile menu and choose "Mobile menu", in the block settings.
- Folio setup says the pages exist, but they are empty. It never overwrites existing pages. Delete (and empty the trash of) the pages with those slugs, or open each page and insert its page pattern from Patterns > Folio pages.
- A filter button shows no projects, or
/work/#webdoesn't select a filter. The cards need the classff-category-followed by exactly the button'sdata-filtervalue, and the address hash must match adata-filtervalue. - The Contact page shows the big "Let’s make it real." footer. Choose the Page with simple footer template in the page's settings (Folio setup does this only for the Contact page it creates).
- The form, or the icon in the copy button, is gone after someone saved the page. Forms and SVG
in Custom HTML need the
unfiltered_htmlcapability, which administrators have on a single site; when other roles (or site admins on multisite) save the page, WordPress strips them. Edit those pages as an administrator, or use a form plugin. - The form "sends" but nothing arrives. It is a demo form; see Forms.
- The clock shows the wrong time. Change
timeZoneinassets/js/main.js(in a child theme). - A page shows the title twice or no title. Pages use a template without a title, because the page patterns start with their own heading. For a plain text page (privacy policy, terms), choose the Page with title template in the page's settings.
License#
MIT, which is compatible with the GPL that WordPress requires (see LICENSE.txt in the theme
folder). Free for personal and commercial use. The project artwork, case study screens, portrait
and icons are included under the same license. Inter Tight (assets/fonts/inter-tight/OFL.txt)
and Instrument Serif (assets/fonts/instrument-serif/OFL.txt) are licensed under the SIL Open
Font License 1.1.