Freeframe

Documentation

Lingua

v1.0.1Updated Oct 17, 2026A bright, friendly website for a language school with in-person and online classes.

Overview#

Lingua is a complete website for a language school that teaches adults in small groups, in person and online, built as a WordPress block theme. The fictional school is Lingua Language School at 14 Harbour Lane, Bristol, teaching Spanish, French, Italian, German, Japanese and Portuguese. The theme has a home page, a courses page with filters, a course page with tabs, a placement test that recommends a course, a teachers page with a language filter, a weekly timetable with filters, a fees and enrolment page with price tabs and a four-step enrolment form, an about page with an FAQ, a contact page and a 404 page. It is version 1.0.0 (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/lingua.

Everything you see is made of core WordPress blocks arranged in block patterns, so you edit text, links, buttons, lists and pictures in the block editor without touching code. Colours, the fonts and font sizes are design tokens in theme.json: change them in Appearance > Editor > Styles and the whole site follows. The course page, the placement test's recommendations and the enrolment form's fees are worked out in the browser by assets/js/main.js from the course data at its top.

Quick start#

Requirements: WordPress 7.1 or newer and PHP 7.4 or newer. No plugins are needed.

  1. Download lingua.zip from freeframe.dev/templates/lingua (the WordPress tab).
  2. 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 Lingua.
  3. Go to Appearance > Lingua setup and click Create the demo pages. This creates the Home, Courses, course, Placement test, Teachers, Timetable, Fees and enrolment, About and Contact pages from the theme's page patterns, sets Home as the front page (Settings > Reading) 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.
  4. In Settings > General, set the Site Title to your school's name and the Tagline to a short description: the header and footer show the title, and the home page's browser title is "Title — Tagline".
  5. Set your courses, teachers and fees (see Courses, teachers and fees).

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:

sh
pnpm install
pnpm dev     # builds the CSS, then starts WordPress with the theme at http://127.0.0.1:9400

pnpm dev signs you in as admin (password password) and creates the demo pages. 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 > Lingua pages) made of section patterns. Every section is also offered on its own in the block inserter under Patterns > Lingua.

Page Slug Page pattern Sections (pattern files in patterns/)
Home / page-home.php home-hero (greetings around a classroom picture), languages, how-it-works, formats, starting-soon, testimonials, team-teaser, placement-cta
Courses /courses/ page-courses.php courses-header, course-list (filters and thirteen course cards), levels
Course /course/ page-course.php course-breadcrumb, course-detail (facts, tabs, fee card), related-courses
Placement test /placement/ page-placement.php placement-header, placement-test (the questions and the result), placement-help
Teachers /teachers/ page-teachers.php teachers-header, teacher-list (filter and eight teachers), teach-with-us
Timetable /timetable/ page-timetable.php timetable-header, timetable (filters and the week)
Fees and enrolment /enrol/ page-enrol.php enrol-header, prices (tabs), enrol-form (the four steps and the summary)
About /about/ page-about.php about-header, story, method, school, faq
Contact /contact/ page-contact.php contact-header, contact (form, map, hours and directions)
404 any missing URL templates/404.html 404

There is one course page: it shows the course named in the URL (/course/?id=fr-a1-evening), as the HTML version does. 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-lingua/
  style.css                 theme header (name, version, requirements, license); no styles
  theme.json                design tokens: palette, fonts, font sizes, layout widths, button
  functions.php             loads the files in inc/
  inc/setup.php             stylesheet and script (with their strings, page and image URLs),
                            editor styles, the `js` class script, theme colour, 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/block-styles.php      button block styles
  inc/site-setup.php        Appearance > Lingua setup: creates the demo pages and the two menus
  templates/                page.html (pages), page-with-title.html, single.html, index.html,
                            archive.html, search.html, 404.html
  parts/                    header.html and footer.html (they show the header and footer patterns)
  patterns/                 every section and page (PHP files with block markup)
  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, with the course data at the top
  assets/icons/             SVG icons for the Icon block
  assets/img/               logo, favicon, social image, classroom and school illustrations, map,
                            teachers/ (the eight portraits)
  assets/fonts/             Lexend and Young Serif 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-lingua.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.txt

Customising#

Brand and colours#

The colours are in theme.json (settings.color.palette) and in Appearance > Editor > Styles > Colors > Palette:

Colour Used for
Base (paper) the page background
Contrast (ink) text, outlines, the cards' offset shadows, dark sections
Paper 2, Line panels and borders
Muted secondary text
Brand (ultramarine), Brand dark, Brand soft the primary button, links, selected choices
Tomato, Tomato ink the decorative accent (eyebrow bubbles, the hand-drawn underline) and accent text, errors
Sun, Mint level badges, the call-to-action band, the success tick

The theme's stylesheet reads every colour from the palette (bg-brand, text-tomato-ink... are the presets), so changing Brand in Styles recolours every button and link at once. Keep enough contrast between Brand and white, and between Tomato ink and Base, for text (4.5:1).

Each language also has a light background and a readable text colour for its chips, cards and timetable entries (tone-es, tone-fr, tone-it, tone-de, tone-ja, tone-pt). They are defined in src/theme.css (--color-es, --color-es-ink...), not in the palette; change them there and run pnpm build.

Typography#

The fonts are Young Serif (headings and display text, font-display) and Lexend (body text, a typeface designed for reading ease), bundled in assets/fonts/ and declared in theme.json (settings.typography.fontFamilies), so no request goes to Google. To use other fonts, install them in Appearance > Editor > Styles > Typography > Fonts (the Font Library) and select them, or replace the fontFace entries in theme.json. The utility classes use the families --wp--preset--font-family--lexend (font-sans) and --wp--preset--font-family--young-serif (font-display), so rename those slugs 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.

Content#

Edit pages in Pages: click any text to change it, select a button to change its link, select a picture 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 > Lingua. Search for "Lingua", "Harbour Lane", "Bristol", the phone number and the email address when you make it yours: they appear in the pages, the header and the footer.

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 a button driven by assets/js/main.js
home-hero the classroom picture with its greeting bubbles an illustration
course-list, timetable the filter forms see Course filters and Timetable
timetable the week and its colour key see Timetable
course-detail the whole course section filled in from the data, see Course page
related-courses the three course cards written by the script for the course shown
placement-test the whole test see Placement test
teacher-list the language filter buttons see Teacher filter
prices the tab buttons see Price tabs
enrol-form the whole enrolment form and its summary see Enrolment form
contact the contact form and its thank-you message see Forms
404 the "404" with speech bubbles an illustration
footer the newsletter form see Forms

Blocks can't store data- attributes, so the theme writes them as classes on blocks and the first lines of assets/js/main.js turn them back into attributes before anything else runs: ffd-name--value becomes data-name="value" (an underscore stands for a space), ffa-name--value becomes the attribute name="value" (ffa-role--tabpanel), and ff-hidden hides a block until the script shows it. In the editor, hidden blocks are shown with a dashed outline. The ones you will meet:

  • Course cards (Article groups in starting-soon and course-list): ffd-course-card, ffd-lang--spanish (a language id), ffd-level--a1 (a1 to c1) and ffd-format--in-person (in-person or online); the filters read them.
  • Teacher cards: ffd-teacher-card and ffd-langs--spanish_portuguese (the languages they teach, separated by underscores).
  • The live counts and empty states: ffd-course-count, ffd-teacher-count, ffd-course-empty.
  • The price panels on the fees page: groups with the anchor panel-group (and panel-private, panel-companies) and the classes ffa-role--tabpanel, ffa-aria-labelledby--tab-group and ff-hidden for the ones shown later.
  • reveal fades a block in as it scrolls into view; link-card on a group makes the whole group clickable through its first link.

Images and artwork#

  • Pictures (the classroom, the online class, the school building) are SVG illustrations in assets/img/, shown with Image blocks inside boxes that crop them (art boxes with an aspect ratio). Replace them with photos from the Media Library (landscape, at least 1200 × 800 px); the crop comes from the box.
  • Teacher portraits are assets/img/teachers/<teacher id>.svg (600 × 500). On the Teachers and Home pages they are Image blocks; replace them there. The course page's "Your teacher" tab is written by the script from the same files: replace the files (same names), or change the path in teacherHTML() in assets/js/main.js.
  • Logo: the header and footer show assets/img/logo.svg in an Image block next to the Site Title block. Replace the image in Appearance > Editor > Patterns > Header (and Footer); the name comes from Settings > General > Site Title.
  • Map: assets/img/map.svg on the contact page; replace it with your own map picture, or an embed from your map provider.
  • Icons are Icon blocks: select one and pick another icon from the Lingua collection (the theme's icons) or WordPress's own. The arrow in buttons is drawn by CSS (icon-after-arrow-right).
  • Social image: assets/img/og-image.png (1200 × 630, source og-image.svg). WordPress doesn't output Open Graph tags by itself; see SEO and sharing.
  • Favicon: assets/img/favicon.svg is used until you set a Site Icon in Settings > General.

The header and footer are template parts (parts/header.html, parts/footer.html) showing the header and footer patterns. Edit them in Appearance > Editor > Patterns > Header / Footer.

The header has two Navigation blocks with their own menus, created by the setup: "Main menu" (Courses, Timetable, Placement test, Teachers, About, Contact) on wide screens and "Mobile menu" (Home, the same pages and Fees & enrolment) in the full-height mobile menu. Edit them in Appearance > Editor > Navigation. WordPress marks the link to the current page; on the course page the script marks Courses, as the HTML version does. The "Enrol now" buttons and the phone number under the mobile menu are blocks in the header pattern.

The footer's link columns are List blocks: click a link to change it, press Enter to add one. The copyright year is added by PHP.

Courses, teachers and fees#

The course page, the related courses, the placement test's recommendations and the enrolment form use the data at the top of assets/js/main.js (part "1. Data"):

Data Meaning
LANGS id (used in URLs and classes), code, name, tone (the colour class suffix)
LEVELS the CEFR levels a1 to c1: code, name, about
KEEP_DEMO_CURRENT, TERM true and { starts: '2027-01-11', ends: '2027-03-26' }: the term the courses belong to. Once it has started, the script moves the term and every course start on by whole weeks to the same term next year (see below). Set it to false once COURSES and TERM hold your real dates
COURSES id, lang, level, format (in-person or online), title, days (0 = Monday … 5 = Saturday), start, end, weeks, starts (first class, YYYY-MM-DD), fee (pounds per term), teacher (a teacher id), seats (places left), room, summary, description
TEACHERS id (also the portrait's file name), name, role, from, years, bio, quote
SYLLABUS, OUTCOMES five modules and four "you'll be able to" lines per level, shared by every language
BOOK_PRICE, CONCESSION the course book (£24) and the concession discount (0.15, 15% off the course fee)

To add a course, add an entry to COURSES with a new id, then add its card to the Courses page (duplicate a card in course-list, change its text, link and ffd- classes), its sessions to the timetable's Custom HTML and its option to the enrolment form's course list (in enrol-form).

js
{ "id": "es-c1-online", "lang": "spanish", "level": "c1", "format": "online", "title": "Advanced Spanish", "days": [3], "start": "19:00", "end": "20:30", "weeks": 10, "starts": "2027-01-14", "fee": 240, "teacher": "tomas-ibarra", "seats": 8, "room": "Online", "summary": "…", "description": "…" }

The pages show the same courses as blocks (cards, the timetable, the fee cards): keep them in step with the data. When you change a course's text in the data, add the new text to freeframe_lingua_script_strings() in inc/setup.php if you translate the site (the script translates the visitor-facing text in the data with it). Make these changes in a child theme so updates don't overwrite them (see Child themes).

Demo dates. The demo is written for the winter term in TERM (11 January to 26 March 2027). While KEEP_DEMO_CURRENT is true and that term has started, the script moves the term and every course's starts forward to the same term the next year, by whole weeks so weekdays never change (11 January 2027 becomes 10 January 2028), so visitors never see a course that has already begun. It also updates the dates written in the patterns: <span data-term="year">, data-term="starts" and data-term="ends" (the Courses and Timetable headers) and data-course-from="<course id>" on each course card's "weeks from" line. Set KEEP_DEMO_CURRENT = false when you enter your real term dates, and keep the dates in the patterns in step with them.

Dates are written as text ("2027-01-11") and formatted without Date objects, so the site shows the same dates in every time zone. Prices are formatted by pounds() (enrolment summary, in pence) and as '£' + fee (cards); change those for another currency.

Features#

Course filters#

The Courses page's filter form (data-course-filters) hides the cards whose ffd-lang--, ffd-level-- and ffd-format-- classes don't match, updates the live count ("Showing 3 of 13 courses", announced to screen readers) and shows the empty state when nothing matches. The filters are written to the URL, so /courses/?lang=french&format=online can be shared. Values in the URL are read in any case (?LANG=French) and ignored unless they are one of the form's options. The language cards on the home page link to /courses/?lang=<id>.

Course page#

/course/?id=<course id> shows one course: the page ships with Spanish for Beginners as a fallback and the script (initCoursePage()) fills in the title, chips, facts, overview, syllabus, teacher, fee, places left, the enrolment and placement links, the page title and three related courses from the data. Ids are read in any case; an unknown id shows the first course.

The overview, syllabus and teacher are accessible tabs (initTabs()): arrow keys move between them, Home and End jump to the first and last, and only the selected tab is in the tab order. "Enrol on this course" opens the enrolment form with the course chosen (/enrol/?course=<id>#enrol).

Placement test#

A Custom HTML block in placement-test (initPlacement()): the visitor chooses a language, then answers ten "can do" statements, each "Not yet" (0), "With some effort" (1) or "Easily" (2). The total (0 to 20) gives the level through PLACEMENT_BANDS (5, 9, 13 and 17 points for A2, B1, B2 and C1). The result names the level and recommends the courses at that level in that language, or the nearest level (the lower one when two are as close) with a note when there is none. Each step moves focus to its question, Back keeps the answers, and a step can't be skipped without an answer.

The result is saved in the browser and shown again on the next visit, with "Start again". /placement/?lang=french starts a new test with French chosen. The statements are in the Custom HTML; keep ten of them, or change PLACEMENT_QUESTIONS and the bands.

Teacher filter#

The buttons (data-teacher-filter, aria-pressed) show the teachers whose ffd-langs-- class includes the language and update the count. /teachers/?lang=japanese opens a language directly.

Timetable#

The week is a Custom HTML block: one column per day, one link per class (data-session with the language, format and time of day: morning, lunchtime or evening). The filters (language, format, time of day or Saturdays) hide the classes that don't match, show "No classes" on empty days, update the count and are kept in the URL (/timetable/?time=saturday). Each class links to its course page.

Price tabs#

The fees page's prices (group courses, private lessons, companies) are tabs like the course page's. /enrol/#private or #companies opens a tab and scrolls to it. To add a tab, add a button in the tab list's Custom HTML (id="tab-online", aria-controls="panel-online") and a group with the anchor panel-online and the classes ffa-role--tabpanel ffa-aria-labelledby--tab-online ffa-tabindex--0 ff-hidden.

Enrolment form#

The enrolment form is a Custom HTML block in enrol-form (initEnrol()), in four steps:

  1. Course: the course list, with its days, times, start date, teacher and places left.
  2. Your details: first and last name, email (checked), an optional phone number (checked when given) and whether the visitor has studied the language before.
  3. Options: pay in full or in three monthly instalments, the concession rate (15% off the course fee) and the course book.
  4. Review: everything chosen, the total and the terms checkbox.

A live summary beside the form shows the course fee, the concession, the book, the total and the three instalments (the last one absorbs the rounding, so they add up exactly). The step list marks the current step (aria-current="step"), errors appear under the fields with focus on the first problem, and focus moves to each step's heading. ?course=<id> (any case) chooses the course.

Nothing is sent. The place to send the enrolment is marked in the submit handler (// To send for real, post the enrolment to your booking system here); replace it with a request to your booking system, and check fees and places again on your server.

Header, mobile menu, scroll reveals and back to top#

  • The header gains a border once the page scrolls.
  • The menu button opens the full-height mobile menu, updates aria-expanded and its label, and Esc closes it and returns focus to the button.
  • Blocks with the reveal class fade in as they scroll into view; revealing one also reveals everything above it. The home page's greeting bubbles bob gently. With reduced motion, or without JavaScript, everything shows at once and nothing moves.
  • A "Back to top" button appears after about one screen of scrolling; it moves focus to the main content and hides while it would cover the sticky fee or enrolment summary. To remove it, delete the "Back to top" part of assets/js/main.js and the .back-to-top styles in src/theme.css, then run pnpm build.

Block styles#

In the block settings' Styles panel:

Block Styles
Button Fill (default, ultramarine), Secondary (white), Sunflower

Forms#

Form Page Checks
Enrolment Fees and enrolment each step
Placement test Placement test an answer on every step
Contact (data-form="contact") Contact name, email, message (at least 20 characters)
Newsletter (data-form="newsletter") footer of every page email

Every form[data-form] is handled by initForms() in assets/js/main.js: errors appear under each field, focus moves to the first problem, and the success message is shown. /contact/?topic=private chooses the contact form's topic (general, courses, placement, private or companies, in any case). The forms don't send anything until you connect them.

Option 1: a form plugin#

Install a form plugin (WPForms, Gravity Forms, Fluent Forms, Contact Form 7, Jetpack Forms...), build the form there, then replace the form's Custom HTML block (and its success message) with the plugin's block. Give its fields the class field and its labels field-label to match the theme.

Option 2: a form service#

Keep the theme's form and send it to a service such as Formspree: in initForms() (in a child theme's copy of assets/js/main.js), replace the comment in the submit handler with a request, and show the success message when it succeeds:

js
fetch('https://formspree.io/f/your-id', { method: 'POST', body: new FormData(form), headers: { Accept: 'application/json' } })
  .then(function (res) {
    if (!res.ok) throw new Error('Request failed');
    form.reset();
    if (success) { success.classList.remove('hidden'); success.focus(); }
  });

and remove the lines after it that show the success message straight away. Branch on form.dataset.form (contact, newsletter) for different endpoints, and update the success messages, which say the forms are demos. The enrolment form posts the same way from its submit handler in initEnrol().

Saved data (localStorage)#

Key Shape
lingua:enrol:v1 { course, first, last, email, phone, experience, plan, concession, book, heard }: the enrolment in progress
lingua:placement:v1 { lang, answers }: the last placement result (ten answers, 0 to 2)

Every read is checked. The enrolment draft (sanitizeDraft()) keeps only a known course id, text up to its field's length without control characters, and known values for the experience, plan and "how did you hear" fields, with true for the checkboxes; anything else is dropped and the stored value repaired. A course in the URL wins over the draft. The draft is cleared after the form is confirmed. A placement result (sanitizePlacement()) needs a known language and exactly ten answers of 0, 1 or 2; otherwise it is removed. Corrupt JSON is removed too. To reset, clear the site's storage in the browser or remove the two keys.

SEO and sharing#

Page titles come from WordPress: the page title, an em dash and the site name (for example "Teachers — Lingua"); the home page shows the site name and tagline (Settings > General); the course page's title follows the course shown. 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. For a school, add your address, phone and courses as structured data (EducationalOrganization and Course) with the SEO plugin.

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 focus rings, the mobile menu button with aria-expanded, accessible tabs, radio groups with legends in the placement test and enrolment form, focus moved to each new step, live counts and fee summaries announced to screen readers, form errors announced through aria-describedby and aria-invalid, alt text on pictures, colour never used alone (every language chip has its name), and reduced-motion support for every animation. The demo passes axe (WCAG 2.1 A and AA).

When you edit: keep one Heading 1 per page, give pictures alt text (or mark them decorative), keep link text meaningful, and label any field you add to a form (<label for="...">).

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/:

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)

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-lingua, 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-lingua. All text in patterns, templates and PHP goes through WordPress's translation functions; the script's strings (course, teacher and syllabus text, labels, fees, messages) are translated in PHP (freeframe_lingua_script_strings() in inc/setup.php). Create a translation with Loco Translate, or with WP-CLI and Poedit:

sh
wp i18n make-pot wp-content/themes/freeframe-lingua wp-content/themes/freeframe-lingua/languages/freeframe-lingua.pot

Save freeframe-lingua-es_ES.mo (for example) in wp-content/languages/themes/ or in the theme's languages/ folder. Page content created by Lingua setup is copied into your pages in the site's language at that moment; translate it in the editor like any other content. Month names are translated with the other strings. Filter values (data-filter, data-teacher-filter, the ffd-… classes, the course ids) stay in English; translate only their labels.

Child themes#

Use a child theme for changes you want to keep when Lingua is updated (your courses in assets/js/main.js especially). Create wp-content/themes/lingua-child/style.css:

css
/*
Theme Name: Lingua Child
Template: freeframe-lingua
*/

Templates, parts, patterns and theme.json in the child theme override the parent's files of the same name. To use your own copy of the script, copy assets/js/main.js into the child theme and, in its functions.php, load it in place of the parent's (wp_deregister_script( 'freeframe-lingua' ) and wp_enqueue_script() on wp_enqueue_scripts, priority 30), keeping the data the parent prints before it (window.freeframeLinguaL10n, window.freeframeLinguaPages, window.freeframeLinguaAssets). 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.

Troubleshooting#

  • The pages look unstyled or new classes do nothing. The stylesheet only contains classes it found when it was built. Run pnpm build after 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 > Lingua.
  • A course card doesn't show up in a filter. Check its ffd-lang--, ffd-level-- and ffd-format-- classes: the values must match the filter's options.
  • The course page shows the wrong course. It uses COURSES in assets/js/main.js, not the blocks on the page; the id in the URL must be a course id.
  • Links from the script go to missing pages. Course cards, the placement result and the course page link to the pages with the demo's slugs (/course/, /enrol/, /courses/, /placement/, /teachers/); if you renamed them, change the slugs back or edit freeframe_lingua_page_urls() in a child theme.
  • Lingua 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 > Lingua pages.
  • An old placement result keeps showing. It is saved in the browser: click "Start again", or open /placement/?lang=<language> to start a new test.
  • 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.

Differences from the HTML version#

The pages, copy, design and interactions are the same. What WordPress changes:

  • Straight apostrophes typed in text blocks are shown curly, as WordPress does everywhere.
  • Data and ARIA attributes on content blocks are written as classes and restored by the script (see Content).
  • Definition lists (the course levels, the about page's figures, the fees' notes, the reception hours) are groups of paragraphs, because blocks can't make <dl> lists.
  • The FAQ's plus-in-a-circle is drawn by CSS on the Details block.

License#

MIT, which is compatible with the GPL that WordPress requires (see LICENSE.txt in the theme folder). Free for personal and commercial use, including the SVG artwork. Lexend and Young Serif are licensed under the SIL Open Font License 1.1 (assets/fonts/*/OFL.txt). Lingua Language School, its teachers and its students are fictional.

Search documentation

Search the guides and every template's documentation