Actionable guidelines for building resilient, progressively-enhanced web applications with HTML5, CSS3, and JavaScript (ES2023) using frameworks such as Next.js and Remix.
You're tired of user complaints about broken JavaScript, accessibility violations, and apps that crumble on slower devices. Your modern React/Next.js setup should make development faster, not create more edge cases to debug.
These Cursor Rules transform how you build web applications by embedding progressive enhancement principles directly into your development workflow—ensuring your apps work universally while still delivering cutting-edge experiences.
Every developer has been there: your sleek single-page application works perfectly in Chrome DevTools, but then real users report blank screens, broken forms, and inaccessible interfaces. You're spending development cycles fixing compatibility issues instead of building features.
The core problem: Most modern development workflows assume JavaScript execution, fast networks, and modern browsers. When those assumptions fail, your entire application fails.
The expensive symptoms:
These rules restructure your development process around a bulletproof foundation: HTML-first, enhancement-layered architecture. Instead of building JavaScript-dependent interfaces and retrofitting accessibility, you start with universally functional markup and progressively enhance.
What this means in practice:
// Instead of this fragile pattern:
function SearchForm() {
const [query, setQuery] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
const results = await fetch('/api/search', {
method: 'POST',
body: JSON.stringify({ query })
});
// Breaks completely if JS fails
};
}
// You get this resilient pattern:
function SearchForm() {
// Works immediately with just HTML
return (
<form action="/search" method="GET">
<input name="q" type="search" required />
<button type="submit">Search</button>
</form>
);
// Enhancement layer adds fetch() + preventDefault()
}
The rules automatically guide you to build the HTML foundation first, then systematically add CSS and JavaScript enhancements with proper feature detection and error boundaries.
Your baseline HTML works on everything—from the latest iPhone to legacy Android devices. No more "works on my machine" scenarios.
Before: 40% of your QA time spent testing device/browser combinations After: Core functionality verified universally; only enhancements need device testing
Progressive enhancement patterns are inherently more maintainable than JavaScript-dependent alternatives.
// Progressive disclosure: 3 lines vs custom accordion component
<details>
<summary>Advanced Options</summary>
<div>Enhanced content here</div>
</details>
Semantic HTML provides accessibility by default. ARIA becomes additive, not corrective.
Critical path optimization happens naturally—HTML renders immediately, enhancements load asynchronously.
Traditional approach: Build controlled React form → add validation → retrofit server endpoint → debug network failures → add loading states → fix accessibility issues
Progressive enhancement approach with these rules:
<form action="/signup" method="post">)// The rules generate this pattern automatically:
export function SignupForm() {
const formRef = useRef<HTMLFormElement>(null);
// Enhancement: intercept for better UX
const handleSubmit = async (e: FormEvent) => {
if (!window.fetch) return; // Let form submit normally
e.preventDefault();
const formData = new FormData(formRef.current!);
try {
await fetch('/signup', { method: 'POST', body: formData });
// Handle success
} catch {
// Fallback: submit form normally
formRef.current?.submit();
}
};
return (
<form ref={formRef} action="/signup" method="post" onSubmit={handleSubmit}>
<input name="email" type="email" required />
<button type="submit">Sign Up</button>
</form>
);
}
Traditional SPA issues: Broken back button, slow initial loads, SEO problems Progressive solution: Server-rendered routes with client-side enhancement
// Next.js/Remix patterns the rules enforce:
// 1. Every route works as a server endpoint
// 2. Client-side navigation enhances but never replaces
// 3. Link prefetching is additive
<Link href="/dashboard" prefetch="intent">
Dashboard {/* Works even if JS fails to load */}
</Link>
Instead of client-side data fetching patterns that show loading spinners, you get:
// Server loader provides immediate content
export async function loader() {
return { products: await getProducts() };
}
// Client enhancement adds real-time updates
export function ProductList({ products }) {
const [liveProducts, setLiveProducts] = useState(products);
useEffect(() => {
if (!('WebSocket' in window)) return; // Graceful degradation
const ws = new WebSocket('/products/live');
ws.onmessage = (event) => setLiveProducts(JSON.parse(event.data));
return () => ws.close();
}, []);
return (
<div>
{liveProducts.map(product => <ProductCard key={product.id} {...product} />)}
</div>
);
}
.cursorrules file in your project rootnpm install --save-dev @axe-core/cli lighthouse
When building components, Cursor now prompts you to:
Example interaction:
You: "Create a modal component"
Cursor (with rules): "I'll create a modal using the <dialog> element for
semantic HTML first, then enhance with JavaScript for better UX. Here's
the progressive approach..."
Add these npm scripts for continuous validation:
{
"scripts": {
"test:baseline": "playwright test --project=no-js",
"test:a11y": "axe-cli http://localhost:3000",
"audit:perf": "lighthouse http://localhost:3000 --chrome-flags='--headless'"
}
}
These rules work with your current Next.js or Remix setup. Start applying them to new features first:
The rules eliminate the complexity of retrofitting progressive enhancement by making it your default development approach. You'll build more resilient applications in less time, with better user experiences across all devices and network conditions.
Your users get applications that work universally. You get a development workflow that scales without breaking.
You are an expert in Progressive Enhancement for modern Web Development using HTML5, CSS3, JavaScript (ES2023), TypeScript, React, Remix, and Next.js.
Key Principles
- Build from a strong, semantic HTML baseline; every user must receive content and primary actions without JavaScript or CSS.
- Layer enhancements: HTML → CSS → JavaScript → Framework/runtime features.
- Detect features, never browsers. Use `@supports`, `@media`, `window.CSS.supports`, or libraries like Modernizr.
- Fail gracefully: if an enhancement is unsupported or script fails, the core experience remains usable.
- Separate concerns: HTML for structure & semantics, CSS for presentation, JavaScript for behavior.
- Ship the smallest, fastest path first: deliver core markup in the initial HTML; defer, async, or lazy-load enhancements.
- Accessibility is non-negotiable: use semantic roles, ARIA only when necessary, and test with keyboard & screen readers.
HTML5 Rules
- Always start with a minimal, semantic outline (`<header>`, `<main>`, `<nav>`, `<footer>`).
- Use progressive disclosure elements (`<details>/<summary>`) before custom JS accordions.
- Provide alternative text for all non-text content.
- Place critical content above the fold in the initial markup; avoid content injection for essentials.
- Never rely on `innerHTML` insertion for unique identifiers; prefer DOM methods or server rendering.
CSS3 / Sass Rules
- Organize styles mobile-first. Use `min-width` media queries for enhancements.
- Feature-query advanced properties:
```css
@supports (display: grid) {
.cards { display: grid; gap: 1rem; }
}
```
- Scope component styles with BEM or CSS Modules to avoid global leakage.
- Load core styles inline (`<style>` or critical CSS) and defer larger bundles with `media="print" onload` pattern.
- Avoid `!important`; refactor specificity instead.
JavaScript (ES2023 / TypeScript)
- Wrap all enhancements in feature checks:
```ts
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').catch(console.error);
}
```
- Use pure functions & modules; avoid global state leakage.
- Prefer `fetch` with `AbortController`; always handle network errors with `.catch` and user feedback.
- Defer scripts (`<script type="module" defer>`). Inline only critical, small scripts (<1 KB) if they unblock UI.
- Respect user settings: check `prefers-reduced-motion`, `prefers-color-scheme` and `save-data`.
Error Handling and Validation
- Guard unsupported APIs first and return early:
```js
export function initShare() {
if (!navigator.share) return; // unsupported, silently ignore
document.querySelector('#share')?.addEventListener('click', () => {
navigator.share({ title: document.title, url: location.href }).catch(console.error);
});
}
```
- Provide server-side fallbacks for client errors (e.g., form submits via `<form>` action + fetch enhancement).
- For forms, validate on both client and server; never assume JS will execute.
- Log enhancement failures to an observability endpoint; never block UI on logging.
Next.js / Remix Framework Rules
- Prefer server components/loaders for core data. Hydrate only interactive islands.
- Export `errorBoundary` in Remix and `error.js` in Next.js to surface user-friendly errors.
- Use `<Link prefetch="intent">` (Remix) or `next/link` with `prefetch` to optimize navigation without breaking basic anchor behavior.
- Keep actions reachable via traditional POST endpoints (`<form method="post">`) so JS navigation failures still work.
- Employ route-based code splitting; verify that dynamic imports do not gatekeep essential content.
Testing
- Baseline tests: load pages with JS disabled (`?nojs=true` env or browser setting) and assert core tasks work.
- Automated cross-browser matrix via BrowserStack; include at least: latest 2 Evergreen, Safari 15+, Chrome 41 (Android 5), IE 11 if required.
- Use Lighthouse CI to guard PWA metrics & accessibility > 90.
- Integrate unit tests for enhancement modules with Jest + JSDOM; mock missing APIs to ensure guards exist.
Performance
- Enforce ≤100 KB critical‐path (HTML+CSS+JS) budget.
- Use `loading="lazy"` for images and `fetchpriority="high"` for above-the-fold hero images.
- Prefetch assets with `<link rel="preload">` only when they are guaranteed to be used—avoid wasting bandwidth on low-capability devices.
- In service workers, cache‐first only static assets; network-first for HTML to keep content fresh.
Accessibility
- Tab order must follow DOM order; avoid positive tabindex.
- Provide visible focus styles; never remove outline without replacement.
- Use ARIA roles only to fill semantic gaps—do not duplicate native semantics.
- Test with NVDA/VoiceOver once per sprint; include `aria-live` regions for dynamic content.
Security
- Escape all user-generated content on the server; never inject HTML directly.
- Use Subresource Integrity (SRI) for third-party scripts.
- Apply Content Security Policy: default-src 'self'; script-src 'self' 'unsafe-inline' if strictly needed.
Directory & Naming Conventions
- `/public` – assets, polyfills, service-worker.
- `/routes` – file-based routing (Next.js/Remix).
- Component filenames: `PascalCase.tsx`; hooks: `useSomething.ts`.
- CSS Modules: `component.module.css`; Sass partials: `_utility.scss`.
Sample Folder Structure
```
app/
├─ components/
│ ├─ Button.tsx # Core button with native <button>
│ └─ Modal.tsx # Requires JS, lazy-loaded
├─ routes/
│ └─ signup.tsx # GET renders form, POST handles submission
├─ styles/
│ ├─ critical.css # inlined in <head>
│ └─ enhancements.scss # deferred
└─ sw.ts # service worker with feature detection
```
Common Pitfalls & How to Avoid
- Relying on client-side routing for every navigation → always provide server routes.
- Tying CSS layout to JS-injected classes → use CSS feature queries instead.
- Assuming `IntersectionObserver` is available → include polyfill or fallback to scroll event throttle.
References & Tooling
- Modernizr, `@supports`, `window.matchMedia` for detection.
- Lighthouse, Axe-core, pa11y for accessibility audits.
- Selenium + BrowserStack for automated cross-browser testing.
- Vite or Next.js built-in analyzer for bundle size enforcement.