Layout

Float-based layouts

Floats were built to wrap text around images, not to lay out pages. AI tools still reach for them — along with the clearfix hack. Grid replaces all of it.

Slop
.sidebar { float: left; width: 250px; }
.main { margin-left: 270px; }
.clearfix::after {
  content: "";
  display: table;
  clear: both;
}
Modern
.layout {
  display: grid;
  grid-template-columns: 250px 1fr;
  gap: var(--space-m);
}

Absolute positioning for layout

Pinning children with position: absolute rips them out of flow and forces fixed parent heights. Let Grid place them instead.

Slop
.parent { position: relative; height: 500px; }
.child {
  position: absolute;
  inset: 0;
}
Modern
.parent { display: grid; }
.child { /* flows naturally in grid */ }

Negative margins for spacing

Negative margins to claw back space are a symptom of margins doing a job gap should own.

Slop
.item { margin-top: -20px; }
Modern
.container { gap: var(--space-m); }

Specificity

!important abuse

Each !important wins one battle and loses the war — the next override needs another one. Use cascade layers to resolve conflicts by design.

Slop
.button { color: red !important; }
Modern
@layer components {
  .button { color: var(--button-color); }
}

ID selectors for styling

An ID is specificity 1-0-0 — almost impossible to override cleanly. Style with classes.

Slop
#header { background: navy; }
Modern
.site-header { background: var(--color-surface); }

Over-qualified & deeply nested selectors

Long descendant chains are brittle and slow. Flatten to a single scoped class.

Slop
div.container > ul.nav > li.nav-item > a.nav-link {
  color: blue;
}
.page .content .sidebar .widget .title span {
  font-size: 14px;
}
Modern
.nav-link { color: var(--color-link); }
.widget-title { font-size: var(--text-sm); }

Values & tokens

Hardcoded pixels & magic numbers

Raw 32px and top: 47px ignore user preferences and trace back to nothing. Every value should map to a token or a relationship.

Slop
h1 { font-size: 32px; }
.container { max-width: 1200px; padding: 20px; }
.dropdown { top: 47px; left: 13px; }
Modern
h1 { font-size: var(--text-3xl); }
.container { max-width: 75rem; padding: var(--space-m); }
.dropdown {
  position: absolute;
  inset-block-start: calc(100% + var(--space-2xs));
  inset-inline-start: 0;
}

Hardcoded hex instead of oklch tokens

Loose hex values break dark mode and theming, and hex/rgb are perceptually uneven. Use oklch() tokens.

Slop
.error { color: #ff0000; border: 1px solid #ff0000; }
:root { --primary: #3366ff; }
Modern
.error {
  color: var(--color-error);
  border: 1px solid var(--color-error);
}
:root { --primary: oklch(55% 0.25 260); }

Responsive

px-based media queries

Pixel breakpoints ignore the user's font size. Use em so layout responds to zoom and preferences.

Slop
@media (max-width: 768px) { }
Modern
@media (max-width: 48em) { }

Media queries for component layout

A component should respond to its own container, not the viewport. That's exactly what container queries are for.

Slop
@media (max-width: 600px) {
  .card { flex-direction: column; }
}
Modern
.card-container { container-type: inline-size; }
@container (inline-size < 400px) {
  .card { flex-direction: column; }
}

Fixed-width containers

A hard width overflows narrow screens. Cap with max-width and let it shrink.

Slop
.container { width: 960px; }
Modern
.container {
  max-width: 60rem;
  width: 100%;
  margin-inline: auto;
}

Animation

Animating layout properties

Transitioning width, height, top, or left triggers layout recalc every frame. Only transform and opacity are composited.

Slop
.element {
  transition: width 300ms, height 300ms,
              top 300ms, left 300ms;
}
Modern
.element {
  transition: transform 300ms, opacity 300ms;
}

Animations with no reduced-motion opt-out

Motion without an escape hatch can make users sick. Always honor prefers-reduced-motion.

Slop
.hero { animation: slide-in 1s ease; }
Modern
.hero { animation: slide-in 1s ease; }
@media (prefers-reduced-motion: reduce) {
  .hero {
    animation-duration: 0.01ms;
    animation-iteration-count: 1;
  }
}

Architecture

No cascade layers

Without layers, every new override starts a specificity arms race. Declare an explicit layer order once.

Slop
.button { background: blue; }
.form .button { background: green; }
.modal .form .button { background: red; }
Modern
@layer components, overrides;

@layer components {
  .button { background: var(--_bg); }
}
@layer overrides {
  .button[data-variant="danger"] {
    --_bg: var(--color-error);
  }
}

@import in stylesheets

@import chains load serially and block rendering. Link stylesheets in parallel from HTML.

Slop
@import url("reset.css");
@import url("components.css");
Modern
<link rel="stylesheet" href="reset.css">
<link rel="stylesheet" href="components.css">

Accessibility

Removing focus outlines

outline: none strands keyboard users. Replace it with a visible :focus-visible ring.

Slop
*:focus { outline: none; }
Modern
:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

Color-only indicators

Color alone is invisible to colorblind users. Pair it with shape, icon, or text.

Slop
.valid { border-color: green; }
.invalid { border-color: red; }
Modern
.invalid {
  border-color: var(--color-error);
  border-inline-start-width: 3px;
}
.invalid::before { content: "⚠ "; }

Tiny touch targets

Sub-44px tap targets are a WCAG miss and a thumb-fumble. Floor them at 44×44.

Slop
.icon-button { width: 24px; height: 24px; }
Modern
.icon-button {
  min-width: 44px;
  min-height: 44px;
  display: grid;
  place-items: center;
}

The AI slop tells

Beyond individual properties, AI-generated CSS has a look. These are the dead giveaways — and the css.dev skills are tuned to avoid all of them.

  • Purple gradient backgrounds on everything
  • linear-gradient(135deg, #667eea 0%, #764ba2 100%) and its cousins
  • Glassmorphism with backdrop-filter: blur() where it adds nothing
  • The same card-grid-with-icon layout for every section
  • box-shadow: 0 10px 40px rgba(0,0,0,0.1) on every surface
  • Inter as the only font choice
  • Hardcoded border-radius: 12px everywhere
  • Fake metrics sections — "10K+ users", "99.9% uptime"
  • Excessive transparency and backdrop-filter
  • rgba() for color manipulation instead of oklch()

FAQ

Why do AI coding tools write bad CSS?

Models are trained on years of public CSS, much of it written before Grid, container queries, cascade layers, and oklch() existed. Left ungoverned, they regress to the statistical average — floats, !important, hardcoded hex, px media queries. The css.dev skills give the model an explicit modern baseline so it stops defaulting to slop.

What should I use instead of float for layout?

CSS Grid for two-dimensional layouts, Flexbox for one-dimensional alignment. A two-column layout is display: grid; grid-template-columns: 250px 1fr; gap: var(--space-m). Floats were a hack for wrapping text around images, not page structure.

How do I avoid !important?

Organize styles with @layer so the cascade resolves conflicts predictably, and use :where() to keep reusable selectors at zero specificity. !important is only acceptable as a last resort against third-party styles you cannot edit.

Should I use oklch() instead of hex colors?

Yes. oklch() is perceptually uniform and covers a wider gamut than hex, rgb, or hsl, so lightening, darkening, and mixing produce predictable, even results. Use it for design tokens and any color manipulation.