Skip to content

← Writing

engineering

Frontend Foundations: The Layer Users Actually Touch

· Jerwin Arnado ·

This kicks off a thirteen-part walk through a full web stack, layer by layer, from the browser tab down to the backups that save you at 3am. Each post is the same shape: what the layer is, why it matters, how I actually build it with Laravel + Vue, and the traps worth knowing. We start where the user starts — the frontend, the only layer they ever touch directly. Everything else in this series is invisible to them; this one is the product as far as they’re concerned.

What “frontend” really means

Three materials, in priority order:

Material Job Failure mode if skipped
HTML structure and meaning screen readers and Google see noise
CSS layout, type, color, motion unstyled, unusable, off-brand
JavaScript interactivity the platform can’t do alone jank, bloat, broken-without-JS pages

The order is the lesson. HTML works with zero CSS and zero JS. CSS enhances structure that already exists. JS is the last resort, not the first reach. Build in that order and you get a page that degrades gracefully; build JS-first and you get a blank <div id="app"> that’s useless the moment a script fails.

Semantic HTML is the cheapest accessibility win

A <div onclick> and a <button> look identical on screen. Only one is keyboard- focusable, announced correctly by a screen reader, and submits a form on Enter. Reach for the real element first:

<!-- structure that means something -->
<header>
  <nav aria-label="Primary"></nav>
</header>
<main>
  <article>
    <h1>One h1 per page</h1>
    <section>
      <h2>Ordered heading levels, never skipped</h2>
    </section>
  </article>
</main>
<footer></footer>

This costs nothing and buys you SEO, accessibility, and a document that’s readable in DevTools. <div> soup costs you all three and you pay it back later with ARIA patches.

CSS: a system, not a pile of overrides

The difference between maintainable and cursed CSS is whether you have a system — tokens for color, spacing, and type that everything draws from — or a thousand one-off values. I use Tailwind because the system is the default: spacing is a scale, color is a palette, and you compose utilities instead of inventing class names and hunting specificity bugs.

<!-- tokens, not magic numbers: spacing on a scale, color from a palette -->
<button class="px-4 py-2 rounded-lg bg-accent text-white hover:bg-accent/90
               focus-visible:ring-2 focus-visible:ring-accent">
  Save
</button>

The rule I hold to: no arbitrary one-off values (p-[13px], random hex codes). The moment you reach for one, the system has a gap — fill the gap in the theme, don’t paper over it inline. This very site is built that way: design tokens live in one @theme block, and markup never carries a raw hex.

JavaScript: spend it like money

Every kilobyte of JS is downloaded, parsed, and executed on a phone that’s slower than your laptop. So I treat the JS budget as a real budget. Two questions before adding any:

  1. Can the platform do this already? A <details> element is a disclosure widget with zero JS. CSS :hover/:focus-within handles most menus. <dialog> is a modal.
  2. Does this need to be interactive at all, or is it static content I’m over-engineering?

When I do need rich interactivity — a real app screen, a complex form — that’s where Vue earns its place: reactive state, components, a clear data flow. But a marketing page or a blog doesn’t need a SPA. Match the tool to the surface:

Surface Right tool
Content site, blog, docs Static HTML/CSS, sprinkle of JS
Interactive widget on a static page Vue component mounted in isolation, or web component
Full application (dashboard, SaaS) Vue SPA or Inertia + Laravel

The build step ties it together

Modern frontend has a build: bundle JS, compile CSS, hash filenames for cache-busting. The ecosystem settled on Vite (we covered leaving webpack behind) — fast dev server, near-instant HMR, sane production output. In a Laravel app, laravel- vite-plugin wires it in; @vite in your Blade template pulls the hashed assets. The win is cache-busting for free: app.a3f9.css changes its hash when content changes, so browsers never serve stale CSS.

Caveats and best practices

  • Progressive enhancement, not graceful degradation. Start with working HTML, layer on CSS, then JS. A page that needs JS to render its content (not just enhance it) is one failed request away from blank.
  • Mobile-first, verified at real widths. I check 360 / 768 / 1024 / 1440 before calling anything done. Tap targets ≥ 44px. No horizontal overflow, ever.
  • Respect prefers-reduced-motion. Motion is an enhancement; disable it fully for users who ask. One media query, no excuses.
  • Contrast is non-negotiable. WCAG AA in both light and dark mode. A pretty palette that fails contrast is a broken palette.

Conclusion

HTML  → structure & meaning      (do this first, always works)
CSS   → a token system           (Tailwind: scale, not magic numbers)
JS    → spend it like money      (platform first, Vue when it earns it)
Build → Vite: bundle + bust cache

The frontend is the only layer your users experience directly, so the bar is high — but the discipline is simple: semantic structure, a real design system, and the least JavaScript that does the job. Next in the series: APIs and backend logic, where we go behind the screen and build the thing the frontend talks to.