Author: D7460N
Context: Architecture principles for framework agnostic, standards-based, role-aware, accessible SPAs
UI (User Interface) is what users see and interact with 🖱️. It’s the visible structure: HTML markup, CSS styles, and controls that expose functionality.
UX (User Experience) is how it all works and feels 🎯—spanning perception, trust, utility, and usability across the user’s journey.
Conflating the two often leads to premature styling, incorrect affordances, and inaccessible workflows that fail when CSS breaks or the user’s context changes (e.g. screen readers, keyboard nav, or degraded network conditions). Separating UI from UX is not just good practice—it’s foundational to resilient, adaptive design 🛠️.
-
UI ≠ Presentation Layer Only
The UI should represent state 🔁, not enforce it. CSS should reflect app state declaratively (e.g.:has([aria-busy="true"])
) without hardcoded logic. -
UX = Behavior + Heuristics
UX is defined by timing, expectations, and reactions ⏱️🤝. This includes:- Loading feedback
- Navigation clarity
- Error prevention and recovery
- Consistency and predictability
- Accessibility at every step ♿ (per WCAG 2.2 and heuristic evaluation standards)
-
🧱 UI lives in HTML/CSS. UX spans across structure, behavior, and content.
-
🧬 UI Layer = HTML + CSS
- All structural semantics handled in HTML (
<nav>
,<section>
,<aside>
, etc.) 🏗️ - All behavior signaling handled in CSS using state selectors like
:has()
,[hidden]
, and container queries 🎛️ - UI updates are a consequence of state—not a controller of it 🎯
- All structural semantics handled in HTML (
-
🧠 UX Layer = Data, Heuristics, and Feedback
- Data fetched and injected via JavaScript 📨
- UX patterns (e.g. CRUD actions, filters, sort) designed for discoverability, clarity, and speed 🔍⚡
- Role-aware flows implemented declaratively: users see only what they can act on 🧑💼
- All feedback (e.g. loading states, errors, empty states) exposed to screen readers and non-visual users 🗣️👂
❌ Bad:
<div class="loading">Loading...</div>
if (loading) document.querySelector(".loading").classList.add("show")
✅ Good (UI separated from UX):
<output aria-busy="true" hidden>Loading data…</output>
:has([aria-busy="true"]) output {
display: block;
}
output.hidden = false
output.setAttribute("aria-busy", true)
Now:
- The UI reacts to the state 🔁.
- The UX (user experience) of “loading” is exposed to all users—visually and via assistive tech 🔊.
🧭 ## Final Principle
UI is how it looks. UX is how it works.
UI changes style. UX changes outcomes.
A screen that "looks right" but can’t be used (or breaks under edge cases) is a UI, but it’s not UX.
Good UX begins where styling ends—with intent, resilience, and inclusiveness built from the architecture up 🏗️🔐.