UI Components with DaisyUI

WPZylos scaffold comes with DaisyUI v5 + Tailwind CSS v4 pre-configured for building beautiful plugin UIs — both admin and frontend.

How It Works

Every WPZylos plugin gets a unique CSS prefix derived from the plugin name. This prevents style conflicts between plugins and with WordPress itself.

Plugin NameCSS PrefixExample Class
First Class Dress Clothingfcds-fcds-btn, fcds-card
My Awesome Pluginmap-map-btn, map-card
WooCommerce Bulk Editorwbe-wbe-btn, wbe-card

Three-Layer CSS Isolation

Layer 1: DaisyUI Component Prefix

All DaisyUI component classes get your plugin's prefix:

@plugin "daisyui" {
  prefix: "fcds-";
}

btn becomes fcds-btn, card becomes fcds-card, etc.

Layer 2: Tailwind Utility Prefix

Standard Tailwind utilities also get the prefix:

@import "tailwindcss" prefix(fcds-);

flex becomes fcds-flex, p-4 becomes fcds-p-4, etc.

Layer 3: Admin Root Scoping

Admin CSS variables are scoped to a container element, preventing WordPress dashboard conflicts:

@plugin "daisyui" {
  prefix: "fcds-";
  root: "#fcds-admin";
}

DaisyUI's CSS variables only apply inside #fcds-admin, not globally on :root.

File Structure

resources/
+-- css/
|   +-- app.css          # Frontend CSS (Tailwind + DaisyUI)
|   +-- admin.css        # Admin CSS (+ root scoping)
+-- js/
|   +-- app.js           # Frontend JS entry
|   +-- admin.js         # Admin JS entry
+-- views/
    +-- admin/
    |   +-- settings.blade.php  # Example admin page
    +-- components/
        +-- card.blade.php      # Reusable card component

Build Pipeline

Development

npm install
npm run dev

Vite starts a dev server with hot module replacement.

Production

npm run build

Compiles CSS/JS to dist/ directory:

dist/
+-- css/
|   +-- app.css
|   +-- admin.css
+-- js/
    +-- app.js
    +-- admin.js

Usage in PHP Views

Admin Pages

Always wrap admin content in the root scope container:

<div id="fcds-admin" class="wrap">
    <h1 class="text-2xl font-bold mb-6">Plugin Settings</h1>

    <div class="fcds-card fcds-bg-base-100 fcds-shadow-xl">
        <div class="fcds-card-body">
            <h2 class="fcds-card-title">General Settings</h2>

            <div class="fcds-form-control w-full max-w-md">
                <label class="fcds-label">
                    <span class="fcds-label-text">API Key</span>
                </label>
                <input type="text" class="fcds-input fcds-input-bordered" />
            </div>

            <button class="fcds-btn fcds-btn-primary">Save</button>
        </div>
    </div>
</div>

Frontend Pages

No root scope needed for frontend — just use prefixed classes:

<div class="fcds-card fcds-bg-base-100 fcds-shadow-lg">
    <div class="fcds-card-body">
        <h3 class="fcds-card-title">Product Card</h3>
        <p>Product description here.</p>
        <div class="fcds-card-actions fcds-justify-end">
            <button class="fcds-btn fcds-btn-primary">Add to Cart</button>
        </div>
    </div>
</div>

Available DaisyUI Components

All standard DaisyUI v5 components are available with your plugin's prefix:

ComponentClassDescription
Buttonprefix-btnButtons with variants (primary, secondary, etc.)
Cardprefix-cardContent container with title, body, actions
Inputprefix-inputText input fields
Textareaprefix-textareaMulti-line text input
Selectprefix-selectDropdown select
Toggleprefix-toggleOn/off switch
Checkboxprefix-checkboxCheckbox input
Tabsprefix-tabsTab navigation
Modalprefix-modalDialog/popup
Alertprefix-alertAlert messages
Badgeprefix-badgeStatus badges
Tableprefix-tableData tables
Navbarprefix-navbarNavigation bar
Drawerprefix-drawerSide panel
Menuprefix-menuDropdown/context menu
Toastprefix-toastNotification toast
Loadingprefix-loadingLoading spinner
Progressprefix-progressProgress bar

See the full list at daisyui.com/components.

Enqueuing Assets

The wpzylos-assets package handles asset enqueuing. In your AssetsServiceProvider:

public function boot(): void
{
    $assets = $this->context->make(AssetManager::class);

    // Admin styles and scripts
    $assets->enqueueStyle('admin', 'dist/css/admin.css')
           ->onlyAdmin()
           ->onPage($this->context->slug());

    $assets->enqueueScript('admin', 'dist/js/admin.js')
           ->onlyAdmin()
           ->onPage($this->context->slug());

    // Frontend styles and scripts
    $assets->enqueueStyle('app', 'dist/css/app.css')
           ->onlyFrontend();

    $assets->enqueueScript('app', 'dist/js/app.js')
           ->onlyFrontend();
}

Theme Customization

Customize DaisyUI's theme colors in your CSS:

@plugin "daisyui" {
  prefix: "fcds-";
}

@theme {
  --color-primary: #4f46e5;
  --color-secondary: #7c3aed;
  --color-accent: #06b6d4;
  --color-neutral: #1f2937;
  --color-base-100: #ffffff;
  --color-info: #3b82f6;
  --color-success: #22c55e;
  --color-warning: #f59e0b;
  --color-error: #ef4444;
}

JavaScript Frameworks

WPZylos scaffold supports Vue.js 3 (default) and React 19 for building interactive admin and frontend UIs.

Vue.js (Default)

Vue is the default framework. The scaffold includes a ready-to-use Vue Options API component.

Admin App Example

<!-- resources/js/components/AdminApp.vue -->
<template>
  <div>
    <div class="fcds-card fcds-bg-base-100 fcds-shadow-xl">
      <div class="fcds-card-body">
        <h2 class="fcds-card-title">Settings</h2>
        <input v-model="apiKey" class="fcds-input fcds-input-bordered" />
        <button @click="save" class="fcds-btn fcds-btn-primary">Save</button>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'AdminApp',
  data() {
    return { apiKey: '' };
  },
  mounted() {
    if (window.myPluginData?.settings) {
      this.apiKey = window.myPluginData.settings.apiKey || '';
    }
  },
  methods: {
    save() {
      const formData = new FormData();
      formData.append('action', 'myplugin_save_settings');
      formData.append('nonce', window.myPluginData?.nonce || '');
      formData.append('settings', JSON.stringify({ apiKey: this.apiKey }));
      fetch(window.ajaxurl, { method: 'POST', body: formData });
    },
  },
};
</script>

Mounting Vue Apps

In resources/js/admin.js:

import '../css/admin.css';
import { createApp } from 'vue';
import AdminApp from './components/AdminApp.vue';

document.addEventListener('DOMContentLoaded', () => {
  const el = document.getElementById('my-plugin-admin-app');
  if (el) {
    createApp(AdminApp).mount(el);
  }
});

In the PHP view:

<div id="fcds-admin" class="wrap">
    <h1>Plugin Settings</h1>
    <div id="my-plugin-admin-app"></div>
    <script>
        window.myPluginData = {
            nonce: '<?php echo wp_create_nonce("myplugin_nonce"); ?>',
            settings: <?php echo wp_json_encode($settings); ?>
        };
    </script>
</div>

React

React is included but commented out by default. To switch:

1. Update vite.config.js

import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
// import vue from '@vitejs/plugin-vue';  // Comment out Vue
import react from '@vitejs/plugin-react';  // Uncomment React

export default defineConfig({
  plugins: [
    tailwindcss(),
    // vue(),     // Comment out
    react(),      // Uncomment
  ],
});

2. Update resources/js/admin.js

import '../css/admin.css';
import React from 'react';
import { createRoot } from 'react-dom/client';
import AdminApp from './components/AdminApp.jsx';

document.addEventListener('DOMContentLoaded', () => {
  const el = document.getElementById('my-plugin-admin-app');
  if (el) {
    createRoot(el).render(<AdminApp />);
  }
});

React Component Example

// resources/js/components/AdminApp.jsx
import { useState, useEffect } from 'react';

export default function AdminApp() {
  const [apiKey, setApiKey] = useState('');

  useEffect(() => {
    if (window.myPluginData?.settings) {
      setApiKey(window.myPluginData.settings.apiKey || '');
    }
  }, []);

  const save = () => {
    const formData = new FormData();
    formData.append('action', 'myplugin_save_settings');
    formData.append('nonce', window.myPluginData?.nonce || '');
    formData.append('settings', JSON.stringify({ apiKey }));
    fetch(window.ajaxurl, { method: 'POST', body: formData });
  };

  return (
    <div className="fcds-card fcds-bg-base-100 fcds-shadow-xl">
      <div className="fcds-card-body">
        <h2 className="fcds-card-title">Settings</h2>
        <input
          value={apiKey}
          onChange={e => setApiKey(e.target.value)}
          className="fcds-input fcds-input-bordered"
        />
        <button onClick={save} className="fcds-btn fcds-btn-primary">Save</button>
      </div>
    </div>
  );
}

WordPress Data Passing

Pass data from PHP to Vue/React using wp_localize_script() or inline <script>:

// Method 1: wp_localize_script (recommended)
wp_localize_script('my-plugin-admin', 'myPluginData', [
    'nonce'    => wp_create_nonce('myplugin_nonce'),
    'ajaxUrl'  => admin_url('admin-ajax.php'),
    'settings' => get_option('myplugin_settings', []),
]);

// Method 2: Using JsMount helper (from wpzylos-views)
$mount = $app->make(\WPZylos\Framework\Views\JsMount::class);
echo $mount->adminMount('my-plugin-admin-app', [
    'nonce'    => wp_create_nonce('myplugin_nonce'),
    'settings' => get_option('myplugin_settings', []),
]);

Framework Comparison

FeatureVue.js 3React 19
DefaultYesNo (opt-in)
Template syntaxHTML-like <template>JSX
API styleOptions APIFunctional + Hooks
CSS class attrclassclassName
Bundle size~33KB gzip~42KB gzip
Vite plugin@vitejs/plugin-vue@vitejs/plugin-react