All skills
mblode avatar

/ui-design

@e7db33d
by Matthew Blodemblode/agent-skills136 stars
12

Designs and builds React/Next/Tailwind UI and audits visual and interaction defects. Use when asked to "build a landing page", "extract our design system", "add dark mode", "make this responsive", "remove UI slop", or "audit this component". For product decisions use product-design; for browser measurements use ui-verification; for motion use ui-animation.

Use this Skill: https://skilld.dev/gh/mblode/agent-skills/ui-design

This session only. Nothing lands on disk.

rulesstates-layout-shift.md

≈1.5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Loading state causes layout shift on data arrival

The skeleton or spinner takes 0x0 (or some height); loaded content takes 200x400; when data arrives every element below it jumps. This is the default Cumulative Layout Shift bug, everywhere: skeletons without min-height, images without width/height, fonts without font-display: swap and size-adjust. Fix is mechanical, one-time per surface.

What goes wrong

A user reads a paragraph above a list whose loading state is <Spinner /> (no fixed height). Data arrives, the list expands to 600px, the paragraph shoves off screen, and the user re-finds their place; CLS regresses on Lighthouse. Same pattern on marketing pages with hero images lacking width/height.

Detection

Surfaces: every loading state, every image, every web font.

Static signals (candidates only):

  1. Skeletons without fixed height. Verify a declared height (h-N, min-h-N, style={{ minHeight }}, fixed row count).
  2. Images without dimensions. <img> and <Image> (next/image): fail if neither width+height nor fill with a sized parent.
  3. Fonts without swap + size-adjust. In next/font/google, next/font/local, @font-face, verify display: "swap" and (ideally) adjustFontFallback.
  4. Conditional content above other content. {!data && <Skeleton h={4} />} then a variable-height <List /> is a CLS bug if heights differ.

Rendered check (decisive):

The greps find missing dimensions; the shift itself is a delta between two rendered boxes, so measure it. A skeleton that declares h-14 still shifts if the loaded row sets 68px, and a skeleton with no declared height does not shift if its parent already reserves the space.

Load the surface with the network throttled so the loading state is observable. Record the bounding box of each skeleton, spinner, or placeholder, then record the same container once data has arrived, and compare heights. Attribute the movement with a PerformanceObserver on layout-shift entries over that window: each entry names the sources that moved and how far. Flag any container whose height changes on data arrival, and report the element, both heights, and the viewport.

The ui-verification skill runs this as its layout-shift probe, holding the data response open rather than throttling the whole network so the shell is not slowed with it. Dispatch to it when an app is running; with no browser the greps below produce candidates and the rule is unknown, never a fail.

Concrete commands:

# Skeletons missing min-height
rg -l 'Skeleton' --type=ts src/ | while read f; do
  rg -B 1 -A 3 '<Skeleton' "$f" | rg -q 'h-|height|min-h' \
    || echo "$f: skeleton without explicit height"
done

# <img> without width/height
rg '<img\s' --type=ts --type=js src/ | rg -v 'width=.*height=|height=.*width='

# next/image without width/height/fill
rg '<Image\s' --type=ts src/ | rg -v 'width=|fill'

# Fonts not using swap
rg 'next/font' --type=ts app/ src/ | rg -v 'display: ["\']swap'

# @font-face without font-display
rg -l '@font-face' --type=css | while read f; do
  rg -q 'font-display' "$f" || echo "$f: @font-face without font-display"
done

False-positive guards:

  • Skip files with // ui-audit-ignore:states-layout-shift.
  • Below-the-fold content-visibility: auto may CLS-shift inside its own subtree (acceptable).
  • Skip components declaring min-height via CSS class (Tailwind min-h-*); inspect class strings first.
  • Skip Storybook fixtures.

Fix

Three patches:

// 1. Skeletons get fixed dimensions matching loaded layout
function InvoiceRowSkeleton() {
  return <li className="h-14 rounded-md bg-muted animate-pulse" />;
  //                  ^ matches loaded row height
}

function InvoiceListSkeleton() {
  return (
    <ul className="space-y-2">
      {Array.from({ length: 5 }).map((_, i) => <InvoiceRowSkeleton key={i} />)}
    </ul>
  );
}

// 2. Images declare intrinsic dimensions
import Image from "next/image";

<Image
  src="/hero.jpg"
  alt="Product hero"
  width={1280}
  height={720}
  priority
/>

// or fill mode with a sized parent
<div className="relative aspect-video">
  <Image src="/hero.jpg" alt="..." fill />
</div>

// 3. Fonts loaded with swap + size-adjust fallback
// app/layout.tsx
import { Inter } from "next/font/google";

const inter = Inter({
  subsets: ["latin"],
  display: "swap",        // shows fallback immediately, swaps when ready
  adjustFontFallback: true, // Next.js auto-tunes fallback metrics to reduce CLS
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={inter.className}>
      <body>{children}</body>
    </html>
  );
}

For dynamic-height content (chat bubble, comment), reserve a reasonable minimum in the skeleton and min-height-match the real content: close enough not to jolt, not exact.

Docs:

Default tier and overrides

Defaults to: fix-this-sprint

Surface overrides:

Surface Tier
Marketing landing (LCP-critical) release-blocker
Checkout (form fields shifting under user's cursor) release-blocker
List / Feed / Inbox fix-this-sprint
Dashboard widget fix-this-sprint
Internal admin backlog

A field that shifts under the cursor mid-click causes mis-clicks on destructive actions, so checkout escalates.

Defer-to (when this is another tool's job)

  • Lighthouse / web-vitals report the CLS metric: this rule prevents the bug at write time, Lighthouse confirms it at runtime. Link out, don't restate.
  • Vercel Speed Insights for field measurement.

Suppression

{/* ui-audit-ignore:states-layout-shift, content-visibility:auto, expected to expand */}
<details>

Source: SKILL.md on GitHub

No alerts7d5 checks · Risk SAFE
  • Gen Agent Trust Hub7d

    A comprehensive UI design and auditing skill that uses standard developer tools and trusted services like Tailwind CSS and placeholder asset APIs to build and verify React/Next.js interfaces. It includes explicit guardrails to prevent instruction following from analyzed project files.

  • Socket7d

    No alerts

  • Snyk7d

    Risk: LOW · No issues

  • Runlayer6mo

    4/5 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at e7db33d. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 24 minutes ago.

Activeupdated last week

README badge

README badge for mblode/agent-skills/ui-design