Core frontend coding standards for HTML, CSS, and JavaScript/TypeScript. Use when writing, reviewing, or refactoring any HTML markup, CSS styles, or JS/TS code in this repo. Triggers on phrases like "follow our frontend guidelines", "review this component", "check my markup", "audit this CSS", "is this idiomatic", or whenever editing files under `src/**`, `public/**`, or any `.html`, `.css`, `.ts`, `.tsx`, `.js`, `.jsx` file. Apply automatically when authoring new components, fixing styles, or refactoring JS logic.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
The command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Showing SKILL.md
SKILL.md
Source instructions · Read-only preview
name
frontend-guidelines
description
Core frontend coding standards for HTML, CSS, and JavaScript/TypeScript. Use when writing, reviewing, or refactoring any HTML markup, CSS styles, or JS/TS code in this repo. Triggers on phrases like "follow our frontend guidelines", "review this component", "check my markup", "audit this CSS", "is this idiomatic", or whenever editing files under `src/**`, `public/**`, or any `.html`, `.css`, `.ts`, `.tsx`, `.js`, `.jsx` file. Apply automatically when authoring new components, fixing styles, or refactoring JS logic.
If a rule here conflicts with .github/copilot-instructions.md or a more specific repo skill (e.g. shadcn, web-design-guidelines), the repo-specific instruction wins. These are baseline craft standards.
Self-check before finishing any task
Before declaring work complete, verify:
HTML uses semantic elements (<main>, <article>, <header>, <time>, <button>, <label>) — no <div> soup
All <img> have meaningful alt, loading="lazy" (below the fold), and explicit width/height
Form controls have explicit <label>; interactive elements have a visible :focus-visible ring
No inline styles or !important added; specificity kept low
Animations primarily on opacity / transform; respect prefers-reduced-motion
Colors use design tokens / CSS variables, not raw hex literals
TypeScript: no any, strict equality (===), prefer unknown + narrowing
JS uses const (or let), never var
Array methods (map/filter/reduce) or for...of over C-style index loops
Functions are pure where reasonable; no needless mutation
No new third-party dependency added for what a 1–3 line helper can do
HTML
Hard rules
Use semantic elements — <main>, <article>, <header>, <nav>, <section>, <time>, <button>, <label>. Never use <div class="button">.
Don't misuse semantics — wrong semantic is worse than neutral. Only wrap content in an element if it matches that element's meaning.
Be terse — omit type="text/css", type="text/javascript", XHTML self-closing slashes, and redundant Content-Type meta when <meta charset> exists.
Always declare<html lang> and <meta charset=utf-8>.
Accessibility is not optional — meaningful alt, real <button> / <a> (never <div role="button">), never rely on color alone, label every form control, ensure visible focus (:focus-visible), and meet WCAG 2.2 AA contrast.
Don't block rendering — use defer (or type="module") for scripts; async only for independent ones. Put non-critical scripts at the end of <body>.
Image performance — always set width/height (prevents CLS), use loading="lazy" below the fold, decoding="async", and fetchpriority="high" for the LCP image.
Quote attributes consistently. HTML5 allows unquoted values, but quoting (<input type="email">) is the team default and is required in JSX.
Examples
<!-- ❌ bad --><divid="main"><divclass="article"><divclass="header"><h1>Post</h1><p>Published: <span>21 Feb 2015</span></p></div></div></div><!-- ✅ good --><main><article><header><h1>Post</h1><p>Published: <timedatetime="2015-02-21">21 Feb 2015</time></p></header></article></main>
Stay in normal flow — avoid position: absolute and display: block overrides when alignment/spacing utilities work.
Prefer Flexbox / Grid for layout.
Keep selectors shallow — if you need >3 combinators or pseudo-classes, add a class instead.
Minimize specificity — no !important, avoid IDs as style hooks. Compose classes (.foo.bar) over overriding.
Don't override styles you just set — write the targeted rule directly (li + li vs li {} then li:first-child {}).
Use inheritance — declare on the parent, not every child.
Use shorthand (padding: 5px 10px 20px, transition: 1s).
Unitless when possible (line-height: 1.5, margin: 0); prefer rem for relative units; use clamp() for fluid type. Seconds over ms.
Animations: transitions over keyframes; primarily animate opacity and transform (compositor-friendly). Always honor @media (prefers-reduced-motion: reduce).
Vendor prefixes: don't hand-write them. Modern targets (last 2 Chrome/Firefox/Safari/Edge) need almost none — Autoprefixer / Lightning CSS handles the rest.
Colors: use design tokens / CSS variables (var(--color-primary)). For new color literals prefer oklch() (perceptually uniform, what Tailwind v4 uses) or hsl() over hex. color-mix() for tints/shades. Reserve raw hex for one-off legacy values.
Use modern layout tools — gap for Flex/Grid spacing (not margins between siblings), logical properties (margin-inline, padding-block) for i18n, container queries (@container) over media queries when scoping to a component.
Don't ship hacks — no commented-out rules left behind, no GPU hacks; use only on elements about to animate, and remove it after.
Readability > micro-perf. JS is rarely the bottleneck. Optimize images, network, DOM reflows, and bundle size — not loop counters.
Pure functions by default — no hidden side effects, return new objects rather than mutating inputs. Use structuredClone(x) for deep copies.
Use natives (Array.from, Object.assign, Object.hasOwn, Object.groupBy, structuredClone, Map, Set, URL, URLSearchParams, AbortController) before reaching for libraries. Don't feature-detect ES2015+ — it's universal.
const > let > var. Never use var in new code.
Strict equality (=== / !==). The only acceptable loose comparison is x == null to check for null-or-undefined, and even that is optional.
Prefer array methods (map / filter / reduce / flatMap) when they express intent. for...of is fine when you need early break, await in the loop, or side effects. Avoid C-style for (let i = 0; i < ...) and forEach with side effects.
Rest/spread instead of arguments and apply().
Arrow functions for lexical this instead of .bind(this).
Avoid nesting — pass functions by reference (.map(String) not .map(x => String(x))).
Optional chaining + nullish coalescing (a?.b ?? fallback) over && chains and || defaults (which mishandle 0 / "").
Object.hasOwn(obj, key) instead of Object.prototype.hasOwnProperty.call(obj, key) or obj.hasOwnProperty(key).
TypeScript rules
No any. Use unknown and narrow, or define a proper type. as casts only at trust boundaries.
Prefer type for unions/aliases, interface for extensible object shapes. Be consistent with file's existing style.
satisfies for literal values that should match a type without widening (e.g. config objects).
readonly on props and config that shouldn't mutate. as const for literal tuples/objects.
Discriminated unions over optional-everywhere shapes for state machines / variant props.
Don't re-export types with export *; be explicit.
Examples
// ❌ imperativeconst result = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] % 2 === 0) result.push(arr[i] * arr[i]);
}
// ✅ declarativeconst result = arr.filter(n => n % 2 === 0).map(n => n * n);
Styling is via Tailwind utility classes, not custom CSS files. The CSS rules above govern any hand-written CSS in src/index.css and theme tokens (which use oklch).
Use the cn() helper from src/lib/utils.ts for conditional class composition.
Plain objects are fine for static records. Use Map when keys are dynamic, non-string, or you need ordered iteration / .size.
Async: prefer async/await over .then() chains. Always handle rejection. Use Promise.all for independent work, Promise.allSettled when partial failure is OK. Pass AbortSignal for cancellable work.
Don't over-curry / over-compose.(a, b) => a + b beats a => b => a + b unless partial application is genuinely needed.
No clever tricks — no ~~n for floor, no void function(){}(), no foo || doSomething() as control flow. Write what you mean.
Small composable helpers over copy-pasted one-liners; don't pull in a library for what 3 lines of native code do.