| name | fexend |
| displayName | Fexend UI (Tailwind v4 + Alpine.js) |
| description | Fexend dashboard component patterns using Tailwind CSS v4 and Alpine.js. Use when creating or modifying HTML pages, components, elements, or styles in the Fexend project — including buttons, cards, forms, badges, dark mode, and Alpine.js interactivity. |
| version | 2.0.0 |
Fexend UI — Tailwind CSS v4 + Alpine.js Guidelines
This skill covers conventions and patterns specific to the Fexend HTML dashboard library.
Stack
- Tailwind CSS v4 — CSS-first config via
@theme {} in src/css/app.css
- Alpine.js v3 — All interactivity is inline; no separate JS source files
- Alpine Collapse plugin — for accordion/collapsible components (bundled)
- jQuery + DataTables — bundled via
src/js/app.js
- Select2 — bundled via
src/js/app.js
- Flatpickr — bundled via
src/js/app.js
- Icons — Tabler Icons as inline SVG
- Font — Lexend (Google Fonts, loaded via CDN)
CSS — Separation of Concerns
CSS is organized into four distinct layers. Place every CSS file in the correct layer:
| Layer | Folder | Barrel | Purpose |
|---|
| Components | src/css/components/ | src/css/components.css | UI components — modal, button, card, dropdown, etc. |
| Forms | src/css/forms/ | src/css/forms.css | Form elements — label, input, checkbox, radio, switch |
| Libs | src/css/libs/ | src/css/libs.css | Third-party library theming — select2, flatpickr, datatable |
| Layouts | src/css/layouts/ | src/css/layouts.css | Page structure — navbar, sidebar, layout wrapper |
| Utilities | src/css/utilities/ | src/css/utilities.css | Base element styles — headings, links, lists, paragraphs |
Rules
- UI components (modal, button, card, badge, tab, table, alert, etc.) →
src/css/components/<name>.css
- Form elements (input fields, labels, checkboxes, radios, toggles) →
src/css/forms/<name>.css
- Third-party library themes (select2 dropdown, flatpickr calendar, datatable UI) →
src/css/libs/<name>.css
- Never mix — do not put form styles in
components/, library styles in forms/, etc.
- Always import the new file in its barrel (
components.css, forms.css, or libs.css)
- Never style bare HTML elements in
components/ or forms/ — always use opt-in classes
Adding CSS — step by step
New UI component:
touch src/css/components/<name>.css
New form element:
touch src/css/forms/<name>.css
New library theme:
touch src/css/libs/<name>.css
Tailwind v4 Setup
Config is in CSS, not JS
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--color-primary: #615fff;
--color-primary-dark: #7c86ff;
}
Dark mode is class-based using .dark on <html>, not media strategy.
Theme Color Tokens
Always use semantic tokens, never raw hex values:
| Token | Light | Dark |
|---|
primary / primary-dark | #615fff | #7c86ff |
secondary / secondary-dark | #9333ea | #c27aff |
success / success-dark | #00c950 | #05df72 |
danger / danger-dark | #fb2c36 | #ff6467 |
warning / warning-dark | #f59e08 | #ffb900 |
info / info-dark | #00b8db | #00d3f2 |
foreground / foreground-dark | #ffffff | #0f172a |
background / background-dark | #f1f5f9 | #020617 |
UI Component Class Naming
Pattern: .{component}, .{component}-{color}, .{component}-{color}-{style}, .{component}-{size}
<button class="button button-primary">Filled</button>
<button class="button button-primary-outline">Outline</button>
<button class="button button-primary-soft">Soft</button>
<button class="button button-sm button-primary">Small</button>
<button class="button button-lg button-danger">Large</button>
<button class="button button-primary button-rounded">Pill</button>
<button class="button button-primary button-block">Full Width</button>
<button class="button button-primary button-icon"><svg>...</svg></button>
<span class="badge badge-primary">Label</span>
Soft
Outline
Pill
Default card
Colored card
Hoverable card
Small padding
Large padding
Skeleton state
Title
Body content
Footer
Table Title
Available colors for components: primary, secondary, success, danger, warning, info, white, dark
Available styles: (none = filled), -outline, -soft
Form Elements
Form elements use opt-in classes — bare input, select, textarea, and label elements are NOT globally styled. Always add the appropriate class.
Basic Usage
<label class="label">Field name</label>
<label class="label label-required">Required field</label>
<label class="label label-error">Error state</label>
<label class="label label-valid">Valid state</label>
<input type="text" class="input" placeholder="Enter text">
<input type="text" class="input input-sm" placeholder="Small">
<input type="text" class="input input-lg" placeholder="Large">
<input type="text" class="input input-rounded" placeholder="Pill shape">
<input = = =>
Option 1
Helper text
This field is required
Looks good!
Form Group
<div class="form-group">
<label class="label label-required" for="email">Email</label>
<input type="email" id="email" class="input input-error" placeholder="you@example.com">
<span class="form-feedback form-feedback-error">Please enter a valid email</span>
</div>
Input with Icons
<div class="input-icon-left">
<span class="input-icon">
<svg class="w-5 h-5"></svg>
</span>
<input type="text" class="input" placeholder="Search...">
</div>
<div class="input-icon-right">
<input type="password" class="input" placeholder="Password">
<span class="input-icon">
<svg class="w-5 h-5"></svg>
</span>
</div>
Input Group (addon)
<div class="input-group">
<span class="input-addon">https://</span>
<input type="text" class="input" placeholder="yoursite.com">
</div>
<div class="input-group">
<input type="text" class="input" placeholder="Amount">
<span class="input-addon">USD</span>
</div>
<div class="input-group">
<span class="input-addon">$</span>
<input type="number" class="input">
<span class="input-addon">.00</span>
</div>
Checkbox
<label class="label-checkbox">
<input type="checkbox" class="checkbox">
Accept terms
</label>
<input type="checkbox" class="checkbox checkbox-success" checked>
<input type="checkbox" class="checkbox checkbox-danger" checked>
<input type="checkbox" class="checkbox checkbox-warning" checked>
<div class="label-checkbox-divider">
<input type="checkbox" class="checkbox">
<div>
<p>Enable notifications</p>
<span>Receive updates via email</span>
</div>
</div>
Option label
Description text
...
...
Radio
<label class="label-radio">
<input type="radio" class="radio" name="group">
Option A
</label>
<input type="radio" class="radio radio-success" checked>
<input type="radio" class="radio radio-danger">
<label class="radio-card">
<input type="radio" name="plan">
<div>Plan name</div>
</label>
<label class="radio-button">
<input type="radio" name="size">
Small
</label>
<label class=>
Medium
Switch (Toggle)
<input type="checkbox" class="switch">
<input type="checkbox" class="switch" checked>
<input type="checkbox" class="switch switch-success" checked>
<input type="checkbox" class="switch switch-danger" checked>
<input type="checkbox" class="switch switch-warning" checked>
<input type="checkbox" class="switch switch-info" checked>
<label class="label-checkbox">
<input type="checkbox" class="switch switch-success">
Enable feature
</label>
Third-Party Libraries
All libraries are bundled in public/js/app.js — no CDN scripts needed on pages.
Select2
<select id="my-select" class="select2">
<option>Option 1</option>
</select>
<script>
$(document).ready(function () {
$('#my-select').select2();
});
</script>
Flatpickr
<input type="text" class="input" id="datepicker" placeholder="Pick a date">
<script>
flatpickr('#datepicker', {
dateFormat: 'Y-m-d',
});
</script>
DataTables
<table id="my-table" class="w-full">
<thead>...</thead>
<tbody>...</tbody>
</table>
<script>
$(document).ready(function () {
$('#my-table').DataTable();
});
</script>
Dark Mode
Class-based — toggle .dark on <html>. Every component must include dark: variants.
.my-component {
@apply bg-foreground dark:bg-foreground-dark text-slate-900 dark:text-slate-100;
}
<div class="bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100">
...
</div>
Page Boilerplate (Alpine.js)
Every page uses this standard setup (src/dashboard.html is the boilerplate reference):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Page Title | Fexend</title>
<link rel="stylesheet" href="/public/css/app.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@100..900&display=swap" rel="stylesheet" />
<script>
if (
localStorage.getItem("darkMode") === "true" ||
(!localStorage.() && .().)
) {
...();
}
Alpine.js Patterns
Toggle visibility
<button @click="open = !open">Toggle</button>
<div x-show="open" x-collapse>Content</div>
Dark mode toggle button
<button @click="toggleDarkMode()">
<svg x-show="!darkMode"></svg>
<svg x-show="darkMode"></svg>
</button>
Component-local state
<div x-data="{ tab: 'overview' }">
<button @click="tab = 'overview'" :class="tab === 'overview' ? 'active' : ''">Overview</button>
<button @click="tab = 'details'" :class="tab === 'details' ? 'active' : ''">Details</button>
<div x-show="tab === 'overview'">...</div>
<div x-show="tab === 'details'">...</div>
</div>
Icons (Tabler Icons)
Use inline SVG. Standard sizing: w-5 h-5 or w-6 h-6. Stroke-based only.
<svg xmlns="http://www.w3.org/2000/svg"
class="w-5 h-5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
</svg>
Responsive Layout Pattern
Mobile-first with Alpine.js breakpoint awareness:
<aside class="hidden lg:block" x-show="!isMobile || sidebarOpen">...</aside>
<div class="mobile-menu lg:hidden">
<button @click="mobileMenuOpen = !mobileMenuOpen">...</button>
</div>
<div class="mobile-menu-popup" x-show="mobileMenuOpen" x-transition>...</div>