🎨 Templates & Theming
PhPstrap uses Bootstrap 5. Keep all visual overrides in /css/custom.css so core and modules can update safely. This guide shows the load order, token strategy, dark mode, and patterns you can reuse.
custom.css. Set colors via CSS variables. Avoid editing vendor files. Prefer utility classes; only add minimal custom classes.
1) Where styles live
- Bootstrap CSS: included in your theme header (usually via
includes/header.php). - Your overrides:
/css/custom.cssloaded after Bootstrap. - Page-local CSS: Only for one-off page helpers; otherwise move to
custom.css.
<!-- includes/header.php -->
<link rel="stylesheet" href="/assets/bootstrap.min.css">
<link rel="stylesheet" href="/css/custom.css">
2) Color system & tokens
Expose a small set of brand variables and let Bootstrap map them to components.
/* /css/custom.css */
/* 2.1 Brand tokens */
:root{
--brand-primary: #0d6efd; /* THEME_COLOR fallback */
--brand-secondary: #6c757d;
--brand-accent: #6f42c1;
--brand-success: #198754;
--brand-warning: #ffc107;
--brand-danger: #dc3545;
--brand-info: #0dcaf0;
/* Surface & text */
--surface-0: #ffffff;
--surface-1: #f8f9fa;
--text-0: #111318;
--text-1: #495057;
/* 2.2 Bootstrap CSS vars override (affects components) */
--bs-primary: var(--brand-primary);
--bs-secondary: var(--brand-secondary);
--bs-link-color: var(--brand-primary);
--bs-link-hover-color: color-mix(in oklab, var(--brand-primary) 80%, black);
}
/* 2.3 Dark mode (auto or attribute) */
@media (prefers-color-scheme: dark){
:root{
--surface-0: #0f1115;
--surface-1: #151922;
--text-0: #e8eaed;
--text-1: #b8c0cc;
}
}
[data-bs-theme="dark"]{
--surface-0:#0f1115; --surface-1:#151922;
--text-0:#e8eaed; --text-1:#b8c0cc;
--bs-body-bg: var(--surface-0);
--bs-body-color: var(--text-0);
}
3) Typography & spacing
Use Bootstrap utilities first (.fs-*, .fw-*, .lh-*, .mb-*). Add minimal global tweaks:
body{ background-color: var(--bs-body-bg, var(--surface-0)); color:var(--bs-body-color, var(--text-0)); }
.lead{ color: var(--text-1); }
/* Optional tighter containers on large screens */
@media (min-width:1200px){ .container{ max-width: 1120px; } }
4) Component polish
Small, reusable touches for cards, buttons, forms.
/* Cards */
.card{ border:1px solid var(--bs-border-color, #dee2e6); }
.card.shadow-sm{ transition: transform .18s ease, box-shadow .18s ease; }
.card.shadow-sm:hover{ transform: translateY(-2px); }
/* Buttons */
.btn-primary{ box-shadow: 0 1px 0 rgba(0,0,0,.05); }
.btn-outline-primary:hover{ background:color-mix(in oklab, var(--brand-primary) 12%, white); }
/* Forms */
.form-control::placeholder{ color: color-mix(in oklab, var(--text-1) 80%, white); }
.input-group-text{ background: transparent; }
/* Badges */
.badge{ letter-spacing:.02em; }
5) Accessibility defaults
- Maintain contrast ≥ 4.5:1 for body text; 3:1 for UI elements.
- Do not remove outlines; restyle them for visibility.
:focus-visible{
outline: 3px solid color-mix(in oklab, var(--brand-primary) 70%, white);
outline-offset: 2px;
border-radius: .5rem;
}
6) Styling the non-logged LexBoard template
The sample landing (public) page uses a few helpers. Move them into custom.css and keep the page clean.
/* === LexBoard public template helpers === */
/* 6.1 Region cards */
.region-card{ border:0; }
.region-card .card-body{ padding:1rem 1.1rem; }
.region-card:hover{ transform: translateY(-2px); }
.region-disabled{ opacity:.55; filter: grayscale(30%); pointer-events:none; }
/* 6.2 KB badge size */
.kb-badge{ font-size:.8rem; letter-spacing:.02em; }
/* 6.3 Hero panel */
.search-hero{
background:
radial-gradient(1200px 600px at 50% -10%, color-mix(in oklab, var(--brand-primary) 8%, transparent), transparent 60%),
linear-gradient(135deg, var(--bs-body-bg) 0%, var(--bs-body-bg) 60%);
border: 1px solid var(--bs-border-color, #dee2e6);
}
/* 6.4 Input group icon alignment */
#lex-search-addon .fas{ opacity:.7; }
Then remove the inline <style> block from the PHP template and keep only semantic markup.
7) Page layout patterns
- Hero:
.container > .rounded-4 .shadow-sm .p-4 .p-md-5 - Grid: Use
.row-cols-*responsive helpers for card collections. - CTAs: Prefer
.btn.btn-primaryfor primary action;.btn-outline-darkfor secondary. - Empty states: Use
.alert.alert-light.borderwith an icon.
8) Dark mode
Support both OS preference and manual toggle. Add a simple body attribute switcher if desired.
// Example toggle (place in a small JS file)
const tgl = document.querySelector('[data-theme-toggle]');
if (tgl){
tgl.addEventListener('click', () => {
const dark = document.documentElement.getAttribute('data-bs-theme') === 'dark';
document.documentElement.setAttribute('data-bs-theme', dark ? 'light' : 'dark');
localStorage.setItem('theme', dark ? 'light' : 'dark');
});
}
(function init(){
const saved = localStorage.getItem('theme');
if (saved) document.documentElement.setAttribute('data-bs-theme', saved);
})();
9) Header, footer, and logos
- Set the logo height via CSS, not HTML attributes.
- Provide an inverted logo for dark backgrounds.
.site-logo{ height: 28px; }
[data-bs-theme="dark"] .site-logo--primary{ display:none; }
[data-bs-theme="dark"] .site-logo--inverted{ display:inline-block; }
10) Performance tips
- Minify
custom.cssfor production. - Avoid heavy shadows/filters on large lists; prefer light borders.
- Scope complex selectors to a page wrapper class (e.g.,
.page-landing).
11) Theming checklist
- ✅ Bootstrap loads first, then
custom.css - ✅ Tokens set in
:rootand dark mode covered - ✅ No vendor edits; all overrides centralized
- ✅ Accessible focus & contrast verified
- ✅ Inline styles moved into
custom.css
12) FAQ
Q: Can I override Bootstrap variables with SCSS?
A: Yes, but to keep things simple, start with plain CSS variables in custom.css. If you move to SCSS, compile to a single CSS file and keep the same load order.
Questions or ideas? Open a discussion or issue: GitHub Issues.