Skip to content


Repository files navigation

Flynt – WordPress Starter Theme for Developers

Fall in love with WordPress (again)

standard-readme compliant Build Status Code Quality

Flynt is a lightning-fast WordPress Starter Theme for component-based development with ACF Pro.



  1. Clone this repo to <your-project>/wp-content/themes.
  2. Change the domain variable in flynt/vite.config.js to match your domain: const wordpressHost = 'http://your-project.test'
  3. Navigate to the theme folder and run the following command in your terminal:
# wp-content/themes/flynt
composer install
npm install
npm run build
  1. Open the WordPress backend and activate the Flynt theme.


To start developing run the following command:

# wp-content/themes/flynt
npm start

All files in assets and Components will now be watched for changes and served. Happy coding!


After developing it is required to generate compiled files in the ./dist folder.

To generate the compiled files, run the following command:

# wp-content/themes/flynt
npm run build

To skip the linting process (optional) and to generate the compiled files, run the command:

# wp-content/themes/flynt
npm run build:production

Base Style

Flynt comes with a ready to use Base Style built according to our best practices for building simple, maintainable components. Go to domain/BaseStyle to see it in action.


The ./assets folder contains all global JavaScript, SCSS, images, and font files for the theme. Files inside this folder are watched for changes and compile to ./dist.

The main.scss file is compiled to ./dist/assets/main.css which is enqueued in the front-end.

The admin.scss file is compiled to ./dist/assets/admin.css which is enqueued in the administrator back-end of WordPress, so styles added to this file will take effect only in the back-end.

Lib & Inc

The ./lib folder includes helper functions and basic setup logic. You will most likely not need to modify any files inside ./lib. All files in the ./lib folder are autoloaded via PSR-4.

The ./inc folder is a more organised version of WordPress' functions.php and contains all custom theme logic. All files in the ./inc folder are automatically required.

For organisation, ./inc has three subfolders. We recommend using these three folders to keep the theme well-structured:

  • customPostTypes
    Use this folder to register custom WordPress post types.
  • customTaxonomies
    Use this folder to register custom WordPress taxonomies.
  • fieldGroups
    Use this folder to register Advanced Custom Fields field groups. (See Field Groups for more information.)

After the files from ./lib and ./inc are loaded, all components from the ./Components folder are loaded.

Page Templates

Flynt uses Timber to structure its page templates and Twig for rendering them. Timber's documentation is extensive and up to date, so be sure to get familiar with it.

As part of the Twig Extension the theme uses a Twig function in to render components into templates:

Besides the main document structure (in ./templates/_document.twig), everything else is a component.


A component is a self-contained building-block. Each component contains its own layout, its ACF fields, PHP logic, scripts, and styles.

  ├── _style.scss
  ├── functions.php
  ├── index.twig
  ├── screenshot.png
  ├── script.js

The functions.php file for every component in the ./Components folder is executed during the WordPress action after_setup_theme. This is run from the ./functions.php file of the theme.

To render components into a template, see Page Templates.

Web Components

Web components provide a standard component model for encapsulation and interoperability HTML elements. Most components are based on an autonomous custom element called flynt-component.

To define the name of a specific component use the name attribute, which should match the component’s folder name, to be ensure that its JavaScript is loaded as specified (see JavaScript modules for more details).

For example:

<flynt-component name="BlockWysiwyg" …></flynt-component>

JavaScript modules

Using a module based approach, allows to breaks JavaScript into separate files and keep them encapsuled inside Components itself.

Different loading strategies can be defined for each component independently when using the custom element flynt-component:

  • load:on="idle"
    Initialises after full page load, when the browser enters idle state.
    Usage example: Elements that don’t need to be interactive immediately.
  • load:on="visible"
    Initialises after the element get visible in the viewport.
    Usage example: Elements that go “below the fold” or should be loaded when the user sees them.
  • load:on="load" (default)
    Initialises immediately when the page loads.
    Usage example: Elements that need to be interactive as soon as possible.
  • load:on:media="(min-width: 1024px)"
    Initialises when the specified media query matches.
    Usage example: Elements which may only be visible on certain screen sizes.


<flynt-component name="BlockWysiwyg" load:on="visible"></flynt-component>

If it makes logical sense, loading strategies can be combined:

<flynt-component name="NavigationMain" load:on="idle" load:on:media="(min-width: 1024px)">

With nested components the loading strategy is waiting for parents. If you have a component with load:on="idle" nested inside a component with load:on="visible", the child component will only be loaded on visible of the parent component.

Advanced Custom Fields

Defining Advanced Custom Fields (ACF) can be done in functions.php for each component. As a best practice, we recommend defining your fields inside a function named getACFLayout() which you can then call in a field group.

For example:

namespace Flynt\Components\BlockWysiwyg;

function getACFLayout()
    return [
        'name' => 'blockWysiwyg',
        'label' => __('Block: Wysiwyg', 'flynt'),
        'sub_fields' => [
                'label' => __('Content', 'flynt'),
                'name' => 'contentHtml',
                'type' => 'wysiwyg',
                'delay' => 0,
                'media_upload' => 0,
                'required' => 1,

Field Groups

Field groups are needed to show registered fields in the WordPress back-end. All field groups are created in the ./inc/fieldGroups folder. Two field groups exist by default: pageComponents.php and postComponents.php.

We call the function getACFLayout() defined in the functions.php file of each component to load fields into a field group.

For example:

use ACFComposer\ACFComposer;
use Flynt\Components;

add_action('Flynt/afterRegisterComponents', function () {
        'name' => 'pageComponents',
        'title' => 'Page Components',
        'style' => 'seamless',
        'fields' => [
                'name' => 'pageComponents',
                'label' => __('Page Components', 'flynt'),
                'type' => 'flexible_content',
                'button_label' => __('Add Component', 'flynt'),
                'layouts' => [
        'location' => [
                    'param' => 'post_type',
                    'operator' => '==',
                    'value' => 'page'

Here we use the ACF Field Group Composer plugin, which provides the advantage that all fields automatically get a unique key.

ACF Option Pages

Flynt includes several utility functions for creating Advanced Custom Fields options pages. Briefly, these are:

  • Flynt\Utils\Options::addTranslatable
    Adds fields into a new group inside the Translatable Options options page. When used with the WPML plugin, these fields will be returned in the current language.
  • Flynt\Utils\Options::addGlobal
    Adds fields into a new group inside the Global Options options page. When used with WPML, these fields will always be returned from the primary language. In this way these fields are global and cannot be translated.
  • Flynt\Utils\Options::getTranslatable
    Retrieve a translatable option.
  • Flynt\Utils\Options::getGlobal
    Retrieve a global option.

Timber Dynamic Resize

Timber provides a resize filter to resize images on first page load. Resizing many images at the same time can result in a server timeout. That's why Flynt provides a resizeDynamic filter, that resizes images asynchronously upon first request of the image itself. Resized images are stored in uploads/resized. To regenerate all image sizes and file versions, delete the folder.

To enable Dynamic Resize, go to Global Options -> Timber Dynamic Resize.

Twig Extensions

readingTime (Type: Filter)

Returns the reading time of a string in minutes.

{{ 'This is a string'|readingTime }}

Example from Components/GridPostsArchive/index.twig

renderComponent($componentName, $data) (Type: Function)

Renders a component. See Page Templates.

{% for component in post.meta('pageComponents') %}
    {{ renderComponent(component) }}
{% endfor %}

Example from templates/page.twig

placeholderImage($width, $height, $color = null) (Type: Function)

Useful in combination with lazysizes for lazy loading. Returns a "data:image/svg+xml;base64" placeholder image.

{{ placeholderImage(768, 512, 'rgba(125, 125, 125, 0.1)') }}

Example from Components/BlockImage/index.twig

resizeDynamic($src, $w, $h = 0, $crop = 'default', $force = false) (Type: Filter)

Resizes an image dynamically. See Timber Dynamic Resize.

{{ post.thumbnail.src|resizeDynamic(1920, (1920 / 3 * 2)|round, 'center') }}

Example from Components/BlockImage/index.twig



In some setups images may not show up, returning a 404 by the server.

The most common reason for this is that you are using nginx and your server is not set up in the the recommended standard. You can see that this is the case, if an image url return a 404 from nginx, not from WordPress itself.

In this case, please add something like

location ~ "^(.*)/wp-content/uploads/(.*)$" {
  try_files $uri $uri/ /index.php$is_args$args;

to your site config.

Other issues might come from Flynt not being able to determine the relative url of your uploads folder. If you have a non-standard WordPress folder structure, or if you use a plugin that manipulates home_url (for example, WPML) this can cause problems when using resizeDynamic.

In this care try to set the relative upload path manually and refresh the permalink settings in the back-end:

add_filter('Flynt/TimberDynamicResize/relativeUploadDir', function () {
    return 'app/uploads'; // Example for Bedrock installs.

SSL certificate for dev server

If you want to use https in development, please define the following variables inside a .env file:



This project is maintained by Bleech.

The main people in charge of this repo are:


To contribute, please use GitHub issues. Pull requests are accepted. Please also take a moment to read the Contributing Guidelines and Code of Conduct.

If editing the README, please conform to the standard-readme specification.


MIT © Bleech