Skip to content
Sage
  • Sage page

Advanced WordPress starter theme with Tailwind CSS and Laravel Blade

Hybrid WordPress theme development — Blade templates, Tailwind CSS, and Vite HMR for editor styles

Advanced WordPress starter theme with Tailwind CSS and Laravel Blade

~/wp-content/themes

$ composer create-project roots/sage my-theme

Creating a "roots/sage" project at "./my-theme"
Installing roots/sage (11.0.4)
  - Downloading roots/sage (11.0.4)
  - Installing roots/sage (11.0.4): Extracting archive
Created project in ./my-theme

$ cd my-theme && npm install && npm run build

added 127 packages in 8s

$ npm run build

> build
> vite build

vite v6.3.6 building for production...
✓ 4 modules transformed.
Generated an empty chunk: "app".
public/build/assets/editor.deps-DxpY22xl.json   0.02 kB │ gzip: 0.04 kB
public/build/manifest.json                      0.67 kB │ gzip: 0.23 kB
public/build/assets/theme.json                 34.56 kB │ gzip: 5.01 kB
public/build/assets/editor-ehnqdtwn.css         5.51 kB │ gzip: 1.83 kB
public/build/assets/app-Hfb9ghGI.css           19.27 kB │ gzip: 5.64 kB
public/build/assets/app-l0sNRNKZ.js             0.00 kB │ gzip: 0.02 kB
public/build/assets/editor-CfU4GbSP.js          0.03 kB │ gzip: 0.05 kB
✓ built in 123ms

Laravel Blade templates with WordPress

Advanced WordPress starter theme with Tailwind CSS and Laravel Blade

Base layout template

resources/views/layouts/app.blade.php

<!doctype html>
<html @php(language_attributes())>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    @php(do_action('get_header'))
    @php(wp_head())

    @vite(['resources/css/app.css', 'resources/js/app.js'])
  </head>

  <body @php(body_class())>
    @php(wp_body_open())

    <div id="app">
      <a class="sr-only focus:not-sr-only" href="#main">
        {{ __('Skip to content', 'sage') }}
      </a>

      @include('sections.header')

      <main id="main" class="main">
        @yield('content')
      </main>

      @hasSection('sidebar')
        <aside class="sidebar">
          @yield('sidebar')
        </aside>
      @endif

      @include('sections.footer')
    </div>

    @php(do_action('get_footer'))
    @php(wp_footer())
  </body>
</html>

Reusable alert component

resources/views/components/alert.blade.php

@props([
  'type' => null,
  'message' => null,
])

@php($class = match ($type) {
  'success' => 'text-green-50 bg-green-400',
  'caution' => 'text-yellow-50 bg-yellow-400',
  'warning' => 'text-red-50 bg-red-400',
  default => 'text-indigo-50 bg-indigo-400',
})

<div {{ $attributes->merge(['class' => "px-2 py-1 {$class}"]) }}>
  {!! $message ?? $slot !!}
</div>

Tailwind CSS synced to WordPress block editor

Advanced WordPress starter theme with Tailwind CSS and Laravel Blade

Tailwind CSS configuration

resources/css/app.css

@import "tailwindcss" theme(static);
@source "../views/";
@source "../../app/";

Generated theme.json

theme.json

{
  "__processed__": "This file was generated using Vite",
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 3,
  "settings": {
    "layout": {
      "contentSize": "48rem"
    },
    "background": {
      "backgroundImage": true
    },
    "color": {
      "custom": false,
      "customDuotone": false,
      "customGradient": false,
      "defaultDuotone": false,
      "defaultGradients": false,
      "defaultPalette": false,
      "duotone": [],
      "palette": [
        {
          "name": "Gray (50)",
          "slug": "gray-50",
          "color": "oklch(98.5% .002 247.839)"
        },
        {
          "name": "Gray (100)",
          "slug": "gray-100",
          "color": "oklch(96.7% .003 264.542)"
        },
        {
          "name": "Gray (200)",
          "slug": "gray-200",
          "color": "oklch(92.8% .006 264.531)"
        },
        {
          "name": "Gray (300)",
          "slug": "gray-300",
          "color": "oklch(87.2% .01 258.338)"
        },
        {
          "name": "Gray (400)",
          "slug": "gray-400",
          "color": "oklch(70.7% .022 261.325)"
        },
        {
          "name": "Gray (500)",
          "slug": "gray-500",
          "color": "oklch(55.1% .027 264.364)"
        },
        {
          "name": "Gray (600)",
          "slug": "gray-600",
          "color": "oklch(44.6% .03 256.802)"
        },
        {
          "name": "Gray (700)",
          "slug": "gray-700",
          "color": "oklch(37.3% .034 259.733)"
        },
        {
          "name": "Gray (800)",
          "slug": "gray-800",
          "color": "oklch(27.8% .033 256.848)"
        },
        {
          "name": "Gray (900)",
          "slug": "gray-900",
          "color": "oklch(21% .034 264.665)"
        },
        {
          "name": "Gray (950)",
          "slug": "gray-950",
          "color": "oklch(13% .028 261.692)"
        },
        {
          "name": "Black",
          "slug": "black",
          "color": "#000"
        },
        {
          "name": "White",
          "slug": "white",
          "color": "#fff"
        }
      ]
    },
    "custom": {
      "spacing": {},
      "typography": {
        "font-size": {},
        "line-height": {}
      }
    },
    "spacing": {
      "padding": true,
      "units": [
        "px",
        "%",
        "em",
        "rem",
        "vw",
        "vh"
      ]
    },
    "typography": {
      "defaultFontSizes": false,
      "customFontSize": false,
      "fontFamilies": [
        {
          "name": "sans",
          "slug": "sans",
          "fontFamily": "ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji"
        },
        {
          "name": "serif",
          "slug": "serif",
          "fontFamily": "ui-serif,Georgia,Cambria,Times New Roman,Times,serif"
        },
        {
          "name": "mono",
          "slug": "mono",
          "fontFamily": "ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace"
        }
      ],
      "fontSizes": [
        {
          "name": "xs",
          "slug": "xs",
          "size": ".75rem"
        },
        {
          "name": "sm",
          "slug": "sm",
          "size": ".875rem"
        },
        {
          "name": "base",
          "slug": "base",
          "size": "1rem"
        },
        {
          "name": "lg",
          "slug": "lg",
          "size": "1.125rem"
        },
        {
          "name": "xl",
          "slug": "xl",
          "size": "1.25rem"
        },
        {
          "name": "2xl",
          "slug": "2xl",
          "size": "1.5rem"
        },
        {
          "name": "3xl",
          "slug": "3xl",
          "size": "1.875rem"
        },
        {
          "name": "4xl",
          "slug": "4xl",
          "size": "2.25rem"
        },
        {
          "name": "5xl",
          "slug": "5xl",
          "size": "3rem"
        },
        {
          "name": "6xl",
          "slug": "6xl",
          "size": "3.75rem"
        },
        {
          "name": "7xl",
          "slug": "7xl",
          "size": "4.5rem"
        },
        {
          "name": "8xl",
          "slug": "8xl",
          "size": "6rem"
        },
        {
          "name": "9xl",
          "slug": "9xl",
          "size": "8rem"
        }
      ]
    }
  }
}

Professional theme structure & modern workflow

Advanced WordPress starter theme with Tailwind CSS and Laravel Blade

Theme structure

my-theme

.
├── app/
│   ├── Providers/
│   ├── View/
│   │   └── Composers/
│   ├── filters.php
│   └── setup.php
├── resources/
│   ├── css/
│   │   ├── app.css
│   │   └── editor.css
│   ├── js/
│   │   ├── app.js
│   │   └── editor.js
│   └── views/
│       ├── components/
│       ├── layouts/
│       ├── partials/
│       └── sections/
├── public/
├── composer.json
├── package.json
├── theme.json
└── vite.config.js

Vite configuration with theme.json generation

vite.config.js

import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite';
import laravel from 'laravel-vite-plugin'
import { wordpressPlugin, wordpressThemeJson } from '@roots/vite-plugin';

export default defineConfig({
  base: '/app/themes/sage/public/build/',
  plugins: [
    tailwindcss(),
    laravel({
      input: [
        'resources/css/app.css',
        'resources/js/app.js',
        'resources/css/editor.css',
        'resources/js/editor.js',
      ],
      refresh: true,
    }),

    wordpressPlugin(),

    // Generate the theme.json file in the public/build/assets directory
    // based on the Tailwind config and the theme.json file from base theme folder
    wordpressThemeJson({
      disableTailwindColors: false,
      disableTailwindFonts: false,
      disableTailwindFontSizes: false,
    }),
  ],
  resolve: {
    alias: {
      '@scripts': '/resources/js',
      '@styles': '/resources/css',
      '@fonts': '/resources/fonts',
      '@images': '/resources/images',
    },
  },
})

Advanced WordPress starter theme with Tailwind CSS and Laravel Blade

Block editor integration with HMR

Hot Module Replacement works in the WordPress block editor. Style changes update instantly without page refreshes, making theme development faster and more enjoyable.

PSR-4 autoloading & Laravel patterns

Organized PHP with namespace autoloading, service providers for extensibility, and view composers for clean data management. All powered by Acorn.

Sponsors

Help support our open-source development efforts

Recommendations

If you have to use WordPress, use Bedrock for your web app and Sage to develop your custom theme. Any Laravel developer will feel at home in these tools.

Rory McDaniel Rory McDaniel

I think 2 years ago I tweeted to y'all to say I love Sage -- now I'm getting started with Bedrock and loving it! thank you for making me feel like WordPress can be sane, secure, and modern.

Michael Snook Michael Snook

In my opinion, the roots.io toolkit is the most sane way to do WordPress in 2023

Andrew Halliwell Andrew Halliwell

Sage has brought WordPress development into the modern age. It's absolutely excellent and we'd have probably moved away from WordPress by now without it. Can't recommend it highly enough.

James Tudsbury James Tudsbury

Subscribe for updates

Join over 8,000 subscribers on our newsletter to get the latest Roots updates and tips on building better WordPress sites

Looking for WordPress plugin recommendations, the newest modern WordPress projects, and general web development tips and articles?

One last step! Check your email for a verification link.