10 KiB
Front-end Guidelines
This is a Laravel 12 e-commerce project using Lunar PHP (headless). These guidelines apply to all front-end work.
Project Language Settings
- Greek register:
singular— use the informal second person (εσύ/σου/σε). e.g. "Η κριτική σου", "Το όνομά σου", "Το email σου"
All UI text written for this project must follow the register above.
Tech Stack
- Laravel 12 + Lunar PHP 1.3 (headless e-commerce)
- Blade for templating (
@extends,@section,@yield, components) - Tailwind CSS v4 via
@tailwindcss/vite— zero config,@import 'tailwindcss'only - Stimulus JS for interactivity — controllers registered in
resources/js/stimulus/index.js - Popover API (native browser) for overlays — no JS overlay libraries
- SQLite database,
npm run devfor local asset compilation
Key File Locations
| What | Where |
|---|---|
| CSS entry point | resources/css/app.css |
| Font definitions | resources/css/fonts.css |
| JS entry point | resources/js/app.js |
| Stimulus controllers | resources/js/stimulus/ |
| JS utilities | resources/js/utils/ |
| Layout | resources/views/layouts/app.blade.php |
| UI components (atomic) | resources/views/components/ui/ |
| Section components | resources/views/components/ |
| Page views | resources/views/{page}/ |
| Public assets | public/images/ |
Design Reference
The Bluebeard template (resources/css/bluebeard.css) is used as a reference only for colors, spacing, typography, and effects. It is never imported or used directly. Everything is translated to Tailwind utilities or custom CSS where unavoidable.
The boboko project at /Users/farenoubi/Projects/boboko-test can be referenced for CSS architecture patterns.
Tailwind vs. Custom CSS
Prefer Tailwind utilities by default. Write custom CSS only when something genuinely cannot be expressed as a utility:
- Pseudo-elements (
::before,::after) with dynamic transforms or transitions - Complex descendant/sibling selectors (e.g.
.group:hover .nav-dropdown) - Keyframe animations
- The underline-slide animation on nav links (background-size trick)
Never write inline CSS (style="..."). Never write custom CSS for something Tailwind already covers.
When a component requires a CSS class (e.g. as a hook for a pseudo-element), still put all properties that can be Tailwind utilities as classes on the element in the component file. The CSS rule should contain only what genuinely cannot be a utility — pseudo-elements, complex selectors, keyframes. Do not default to putting all styles in the CSS class just because the class exists.
When custom CSS is needed, add it to resources/css/app.css inside @layer components. Keep the rule minimal — only what can't be a utility.
Design Tokens
Defined in @theme {} in app.css:
--font-sans: Manrope (body text)--font-display: Manrope (headings, nav, buttons) — kept separate from--font-sansin case they diverge later
Reuse existing colors (neutral-200, black), font sizes, spacing, and border styles as components are built. Do not invent new values — check what's already in use first.
Component Structure
resources/views/components/ui/ — atomic, reusable UI elements
Single-purpose, stateless or minimally stateful elements used across many contexts:
button.blade.php✓input.blade.phpinput-group.blade.phpicon.blade.phpimage.blade.phptabs.blade.php/tab.blade.phpaccordion.blade.phptooltip.blade.phpbadge.blade.php- etc.
resources/views/components/ — section-level or composite components
Larger reusable blocks made of several elements, often with real content:
header.blade.php✓footer.blade.phpcta.blade.phpproduct-card.blade.phpcontact-section.blade.php- etc.
Interactivity
Popover API for overlays
For tooltips, modals, dropdowns, and any overlay: use the native Popover API with CSS — no JS, no libraries.
<button popovertarget="my-popover">Open</button>
<div id="my-popover" popover>...</div>
Style open/closed states with [popover] and :popover-open selectors in CSS.
Stimulus JS for behavior
Use Stimulus when JS is genuinely needed (form handling, cart updates, dynamic state, etc.).
- Keep controllers lean: manage state and coordinate the DOM, don't build UI inside them
- Use Stimulus values, targets, and outlets — avoid reading from the DOM imperatively
- Never inject HTML strings or classes from inside a controller. Instead, drive appearance via data attributes or CSS classes toggled on existing elements, and let CSS handle the visual result
- Never write inline JS (
onclick="...", etc.)
Pure CSS for simple interactions
Hover dropdowns, focus states, active states — handle with CSS (:hover, :focus-within, .group:hover) before reaching for Stimulus.
Accessibility (ARIA)
Always add ARIA attributes. This is not optional:
- Interactive elements:
aria-label,aria-expanded,aria-controls,aria-haspopupas appropriate - Images: meaningful
alttext, oralt=""+aria-hidden="true"for decorative images - Icons used as buttons:
aria-labelon the button,aria-hidden="true"on the SVG - Form fields: always associated
<label>,aria-describedbyfor hints/errors - Dynamic content:
aria-liveregions where content updates without navigation - When refactoring or adding interactivity, review ARIA impact — don't break existing roles
Greek Typography
CSS text-transform: uppercase leaves Greek tonos accents in place (e.g. Ά instead of Α), which is incorrect. The utility at resources/js/utils/strip-accents.js handles this automatically for any element with the uppercase Tailwind class. Always add the uppercase class to elements rather than applying text-transform: uppercase in CSS — this keeps the strip-accents utility working without extra configuration.
Performance & Core Web Vitals
Every frontend change should assume it will be measured by PageSpeed/Lighthouse. These rules exist because we hit each of these mistakes in production and had to fix them after the fact — bake them in up front instead.
CSS bundling
- Before adding a CSS import to a shared/global entry point (e.g.
app.css), ask whether every page actually needs it. Page- or component-specific styles (date pickers, legal-page typography, admin-only widgets) should not ship on pages that don't use them. - Scope non-critical CSS to load only where it's used — either a dedicated build entry for that page, or import the
.cssfile inside the same JS module that already lazy-loads that component (e.g. a Stimulus/JS controller), so the bundler emits it as a separate chunk fetched only when that component actually mounts. - Exception: never defer CSS for anything the page shows immediately on load (above-the-fold content, or a component that renders as soon as its JS runs — an FAQ accordion, a cookie-consent banner). Even a well-scoped async CSS chunk arrives a beat after the JS that displays the element, causing a visible flash of unstyled/wrong-state content and a layout shift. Keep that CSS in the blocking bundle on purpose.
- Never import a full icon-font library (Phosphor, Font Awesome, etc.) — it ships every icon regardless of usage. Use inline SVGs for only the icons actually referenced; check the shared icon component for an existing icon before adding a new one.
- Only declare design tokens / CSS custom properties that are actually used somewhere in the codebase (Tailwind v4 already tree-shakes unused
@themetokens from output, so this is for keeping the source legible, not for bytes).
Fonts
font-display: swapmeans any text using a font weight will visibly reflow once that weight's file finishes downloading, if the fallback font's metrics differ. Preload every font weight used in above-the-fold text, not just the default body-copy weight — bold/black heading weights are usually the most visually prominent element on the page and cause the most visible reflow if not preloaded.- When introducing a new heavy-weight text utility (e.g. a bold/black heading class) in a hero or above-the-fold component, confirm that weight has a matching
<link rel="preload" as="font">in the layout<head>.
Images
- Any image that could be the Largest Contentful Paint element (hero/banner images) must have:
fetchpriority="high",loading="eager", explicitwidth/height, a responsivesrcset/sizes, and a<link rel="preload" as="image">in<head>with matchingimagesrcset/imagesizes— this lets the browser start the fetch as soon as it parses<head>, instead of waiting to discover the<img>tag in the body. - Every other image:
loading="lazy"with explicitwidth/heightto reserve its layout space.
Layout shift (CLS)
- Never animate
top/bottom/left/right/width/height/marginon any element that appears or moves without direct user interaction (banners, toasts, slide-in modals, accordions). These are layout-affecting properties — the browser recalculates layout on every animation frame, and each frame's position delta counts toward CLS. Animatetransform(translate/scale) andopacityinstead — both are compositor-only and fully excluded from CLS scoring, no matter how far or long the animation runs. - This applies to vendor/third-party CSS too (cookie-consent widgets, modal libraries) — check its default show/hide mechanism before shipping, and override it if it animates a layout property directly.
- Any element with a collapsed/expanded default state (accordions, dropdowns) needs its collapsed-state CSS in the blocking stylesheet, never a lazily-loaded chunk — otherwise it renders in its expanded/default state for a frame before the async CSS applies.
Forms
Never add novalidate to forms. Always use the browser's default validation.
Asking Questions
When the approach is unclear — layout structure, whether something should be a component, which Tailwind pattern fits — ask before building. A short question is cheaper than a refactor.