agentleFS
sabahattinksabahattink/antigravity-fullstack-hq/skills/web-design-guidelines/SKILL.md

web-design-guidelines

Skill30 starsChanged 53 days agoMITView on GitHub

Web design best practices, accessibility, responsive layout, color contrast. Use when auditing a UI for a11y compliance, designing responsive layouts, or establishing design standards across a web app.

  • Installs packages

176 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use canvas-procedural-animation, intelligent-routing and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • LinHao-citySkill

    canvas-procedural-animation

    Use p5.js/canvas for local procedural character effects: particles, weather, squash/stretch, walk cycles, and environmental motion.

    Also in 5 projects that use this

  • SKB3002Skill

    intelligent-routing

    Automatic agent selection and intelligent task routing. Analyzes user requests and automatically selects the best specialist agent(s) without requiring explicit user mentions.

    Also in 5 projects that use this

  • VercelSkill

    vercel-optimize

    Use for Vercel cost and performance optimization on deployed projects, especially Next.js, SvelteKit, Nuxt, and limited Astro apps. Collect Vercel metrics, usage, project config, and code scan results first; investigate only metric-backed candidates; produce ranked recommendations grounded in verified files and version-aware Vercel/framework docs. Trigger for Vercel bill reduction, slow or expensive routes, caching opportunities, Function Invocations, Build Minutes, Fast Data Transfer, Core Web Vitals, Bot Management, Fluid compute, or cost breakdown requests.

    Also in 15 projects that use this

  • thongdn-itSkill

    nuqs

    nuqs (type-safe URL query state) best practices for Next.js applications. This skill should be used when writing, reviewing, or refactoring code that uses nuqs for URL state management. Triggers on tasks involving useQueryState, useQueryStates, search params, URL state, query parameters, nuqs parsers, or Next.js routing with state.

    Also in 4 projects that use this

  • LinHao-citySkill

    pose-library-design

    Design reusable 2D character pose libraries, action cycles, and expression states for data-driven animation.

    Also in 4 projects that use this

  • stophobiaSkill

    surprise-me

    Create a delightful, unexpected "wow" experience for the user by dynamically discovering and creatively combining other enabled skills. Triggers when the user says "surprise me" or any request expressing a desire for an unexpected creative showcase. Also triggers when the user is bored, wants inspiration, or asks for "something interesting".

    Also in 4 projects that use this

  • +2 more from LinHao-city

The file

We split this skill into the rules it gives an agent

8 rules. 0 are common, 3 are distinctive, and 5 are very specific to this project.

  • 0

    Common

    Most of the 173 edited versions we read keep these.

  • 3

    Distinctive

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Never remove outline on focus without providing an equivalent visual indicator

    line 367
  2. Distinctive:

    Never use color alone to convey information (add icons, text, or patterns)

    line 368
  3. Very specific:

    Never set font-size below 14px (16px minimum for body text)

    line 369
  4. Very specific:

    Never use placeholder as a label substitute — placeholders disappear on input

    line 370
  5. Very specific:

    Never disable zoom (user-scalable=no) — breaks accessibility for low-vision users

    line 371
  6. Very specific:

    Never use tabindex values > 0 — they break the natural tab order

    line 372
  7. Distinctive:

    Never let hover-only tooltips carry essential information (mobile has no hover)

    line 373
  8. Distinctive:

    Never auto-play audio or video with sound without user consent

    line 374

“Common” is measured: kept by at least 6 in 10 of the 173 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Web Design Guidelines

Accessibility (WCAG 2.1 AA)

Color Contrast Requirements

  • Normal text (< 18pt): minimum 4.5:1 contrast ratio
  • Large text (≥ 18pt or 14pt bold): minimum 3:1
  • UI components and graphical objects: minimum 3:1
// Use a utility to check contrast at runtime in dev
// Install: npm i color2k

import { parseToRgb, getLuminance } from 'color2k'

function getContrastRatio(fg: string, bg: string): number {
  const l1 = getLuminance(fg)
  const l2 = getLuminance(bg)
  const lighter = Math.max(l1, l2)
  const darker  = Math.min(l1, l2)
  return (lighter + 0.05) / (darker + 0.05)
}

// In Storybook stories or tests:
// expect(getContrastRatio('#2563eb', '#ffffff')).toBeGreaterThan(4.5)

Semantic HTML

// Good: semantic structure
export function PageLayout() {
  return (
    <>
      <header role="banner">
        <nav aria-label="Main navigation">
          <ul role="list">
            <li><a href="/">Home</a></li>
            <li><a href="/about">About</a></li>
          </ul>
        </nav>
      </header>

      <main id="main-content" tabIndex={-1}>
        <h1>Page Title</h1>
        {/* Skip-to-main link target */}
      </main>

      <aside aria-label="Related content">
        {/* Sidebar */}
      </aside>

      <footer role="contentinfo">
        {/* Footer */}
      </footer>
    </>
  )
}

// Skip link — must be the very first focusable element
function SkipToMain() {
  return (
    <a
      href="#main-content"
      className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-white focus:text-blue-600 focus:rounded-lg focus:shadow-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
    >
      Skip to main content
    </a>
  )
}

Keyboard Navigation

// All interactive elements must be keyboard-operable
function DropdownMenu({ items }: { items: MenuItem[] }) {
  const [open, setOpen] = React.useState(false)
  const [activeIndex, setActiveIndex] = React.useState(-1)

  const handleKeyDown = (e: React.KeyboardEvent) => {
    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault()
        setActiveIndex(i => Math.min(i + 1, items.length - 1))
        break
      case 'ArrowUp':
        e.preventDefault()
        setActiveIndex(i => Math.max(i - 1, 0))
        break
      case 'Escape':
        setOpen(false)
        break
      case 'Enter':
      case ' ':
        if (activeIndex >= 0) items[activeIndex].action()
        break
    }
  }

  return (
    <div role="navigation" aria-label="Actions menu">
      <button
        aria-haspopup="true"
        aria-expanded={open}
        onClick={() => setOpen(o => !o)}
      >
        Actions
      </button>
      {open && (
        <ul
          role="menu"
          onKeyDown={handleKeyDown}
          className="..."
        >
          {items.map((item, i) => (
            <li key={item.id} role="menuitem" tabIndex={i === activeIndex ? 0 : -1}>
              <button onClick={item.action}>{item.label}</button>
            </li>
          ))}
        </ul>
      )}
    </div>
  )
}

ARIA Labels

// Always label icon-only buttons
<button aria-label="Close dialog" onClick={onClose}>
  <XIcon aria-hidden="true" className="h-5 w-5" />
</button>

// Images
<img src={product.image} alt={`${product.name} — front view`} />
// Decorative images get empty alt
<img src="/divider.svg" alt="" role="presentation" />

// Live regions for dynamic content
<div aria-live="polite" aria-atomic="true" className="sr-only">
  {statusMessage}
</div>

Responsive Layout

Mobile-First Approach

/* Write base styles for mobile, then progressively enhance */
.card {
  padding: 1rem;       /* mobile */
}

@media (min-width: 640px) {   /* sm */
  .card { padding: 1.5rem; }
}

@media (min-width: 1024px) {  /* lg */
  .card { padding: 2rem; }
}
// Tailwind mobile-first
<div className="px-4 sm:px-6 lg:px-8">
  <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
    {items.map(item => <Item key={item.id} {...item} />)}
  </div>
</div>

Fluid Typography

/* clamp(min, preferred, max) — scales smoothly between viewport sizes */
:root {
  --text-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
  --text-lg:   clamp(1.125rem, 1rem + 0.75vw, 1.375rem);
  --text-xl:   clamp(1.25rem, 1rem + 1.5vw, 1.75rem);
  --text-2xl:  clamp(1.5rem, 1rem + 2.5vw, 2.25rem);
  --text-4xl:  clamp(2rem, 1rem + 5vw, 4rem);
}

Container Queries

/* Style based on parent width, not viewport width */
.card-grid {
  container-type: inline-size;
  container-name: card-grid;
}

@container card-grid (min-width: 480px) {
  .product-card {
    display: grid;
    grid-template-columns: 140px 1fr;
  }
}

Image Optimization

// Next.js Image — always use this over <img>
import Image from 'next/image'

// Known dimensions (static assets)
<Image
  src="/hero.jpg"
  alt="Dashboard overview showing key metrics"
  width={1200}
  height={630}
  priority  // LCP image: load eagerly
  className="rounded-xl object-cover"
/>

// Unknown dimensions (user uploads)
<div className="relative aspect-video w-full overflow-hidden rounded-xl">
  <Image
    src={user.avatar}
    alt={`${user.name}'s profile photo`}
    fill
    sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
    className="object-cover"
  />
</div>

Touch Targets

Minimum touch target size: 44×44 px (Apple HIG) / 48×48 dp (Material).

// Expand click area without changing visual size
<button
  className="
    relative p-2 -m-2
    /* visual size stays 24px, but hit area is 44px (24 + 2*8 = 40... close enough) */
    before:absolute before:inset-0 before:-m-2
  "
>
  <MenuIcon className="h-6 w-6" />
</button>

// Or just give buttons adequate padding
<button className="min-h-[44px] min-w-[44px] px-4 py-2.5">
  Submit
</button>

Focus Management

// Trap focus inside modals
import { useEffect, useRef } from 'react'

function useFocusTrap(active: boolean) {
  const containerRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    if (!active || !containerRef.current) return

    const focusable = containerRef.current.querySelectorAll<HTMLElement>(
      'a[href], button:not([disabled]), input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])'
    )
    const first = focusable[0]
    const last  = focusable[focusable.length - 1]

    first?.focus()

    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key !== 'Tab') return
      if (e.shiftKey) {
        if (document.activeElement === first) {
          e.preventDefault()
          last?.focus()
        }
      } else {
        if (document.activeElement === last) {
          e.preventDefault()
          first?.focus()
        }
      }
    }

    document.addEventListener('keydown', handleKeyDown)
    return () => document.removeEventListener('keydown', handleKeyDown)
  }, [active])

  return containerRef
}

Performance

Core Web Vitals Targets

MetricGoodNeeds Improvement
LCP (Largest Contentful Paint)< 2.5s2.5–4.0s
INP (Interaction to Next Paint)< 200ms200–500ms
CLS (Cumulative Layout Shift)< 0.10.1–0.25
// Prevent CLS: always reserve space for images/embeds
<div className="aspect-video w-full bg-gray-100 rounded-xl overflow-hidden">
  <Image src={src} alt={alt} fill className="object-cover" />
</div>

// Prevent CLS: skeleton loaders match exact dimensions
function SkeletonCard() {
  return (
    <div className="rounded-xl border border-gray-100 p-6 animate-pulse">
      <div className="h-6 bg-gray-200 rounded w-3/4 mb-3" />
      <div className="h-4 bg-gray-200 rounded w-full mb-2" />
      <div className="h-4 bg-gray-200 rounded w-5/6" />
    </div>
  )
}

Dark Mode

// tailwind.config.ts
const config = {
  darkMode: 'class', // Toggle via class on <html>
  theme: {
    extend: {
      colors: {
        background: 'hsl(var(--background))',
        foreground: 'hsl(var(--foreground))',
      },
    },
  },
}

// globals.css
// :root {
//   --background: 0 0% 100%;
//   --foreground: 222 47% 11%;
// }
// .dark {
//   --background: 222 47% 4%;
//   --foreground: 210 40% 98%;
// }

// Toggle hook
function useDarkMode() {
  const [dark, setDark] = React.useState(() =>
    window.matchMedia('(prefers-color-scheme: dark)').matches
  )

  useEffect(() => {
    document.documentElement.classList.toggle('dark', dark)
    localStorage.setItem('theme', dark ? 'dark' : 'light')
  }, [dark])

  return { dark, toggle: () => setDark(d => !d) }
}

Forbidden Patterns

  • Never remove outline on focus without providing an equivalent visual indicator
  • Never use color alone to convey information (add icons, text, or patterns)
  • Never set font-size below 14px (16px minimum for body text)
  • Never use placeholder as a label substitute — placeholders disappear on input
  • Never disable zoom (user-scalable=no) — breaks accessibility for low-vision users
  • Never use tabindex values > 0 — they break the natural tab order
  • Never let hover-only tooltips carry essential information (mobile has no hover)
  • Never auto-play audio or video with sound without user consent

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.