agentsclimarketplace

Vanilla webpage

Skill aiskillstore/marketplace/skills/yuzu-octopus/vanilla-webpage

Security-audited skills for Claude, Codex & Claude Code. One-click install, quality verified.

Install
npx -y skills add aiskillstore/marketplace --skill vanilla-webpage

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.

What its author says it does

Copied from the file, not written here

Build webpages with vanilla HTML/CSS/JS — no frameworks, no build steps. Covers modern CSS (nesting, @layer, :has, clamp, container queries), vanilla JS, design tokens, accessibility, responsive design.

SKILL.md

47.4 KB, ~12.7k tokens by cl100k_base, as published. Nobody here has run it

Vanilla Webpage Skill

Build webpages with vanilla HTML, CSS, and JavaScript. No frameworks, no build steps, no bloat. Modern CSS is powerful enough. Patterns are reference material, not a menu — use the Ponytail Ladder to decide what you actually need.

Quick Routing

I need to...Go to
Decide if I need a framework§1 Philosophy + §2 Decision Framework
Write semantic HTML§4 HTML Patterns
Style with CSS (custom properties, nesting, @layer)§5 CSS Patterns
Use modern CSS (clamp, :has, container queries)§5 CSS Patterns
Add interactivity with JS§6 JavaScript Patterns
Choose a classless CSS framework§7 Framework Allowlist
Avoid common mistakes§8 Anti-Patterns
Check design tokens and spacing§9 Design Quick Reference
Verify accessibility§10 Accessibility Minimums
Additional CSS/JS patterns§10b Additional Techniques
Browse working code examples§11 Cookbook Reference
Research framework docs or CSS techniques§12 Documentation & Research Access

§12 Documentation & Research Access

Always research before writing code. Use MCP tools first, fall back to web search.

MCP Tools (Preferred)

ToolUse forHow
Context7Framework documentationmcp__context7__resolve-library-idmcp__context7__query-docs
ExaWeb search for techniquesmcp__exa__web_search_exa with semantic query
ExaFetch specific URLsmcp__exa__web_fetch_exa with URL list
PlaywrightVisual verificationmcp__playwright__browser_navigatemcp__playwright__browser_snapshot

Context7 Workflow (for framework docs)

1. resolve-library-id("basecoat ui") → returns library ID
2. query-docs(libraryId, "button component usage") → returns docs

Use for: Pico CSS, Basecoat UI, Flowbite, UIkit, HTMX, or any framework in §7.

Exa Workflow (for technique research)

1. web_search_exa("CSS container query examples 2025") → returns articles
2. web_fetch_exa(["https://example.com/article"]) → returns full content

Use for: Modern CSS techniques, browser support data, best practices, code examples from blogs.

Fallback: Native WebSearch/WebFetch

If MCP tools are unavailable:

1. WebSearch("technique name + year") → find articles
2. WebFetch(url) → extract content

When to Research

  • Before using a framework from §7: Fetch its latest docs via Context7
  • Before using a cutting-edge CSS feature: Verify browser support via Exa or WebSearch
  • When unsure about syntax: Search for working examples
  • When debugging: Search for error messages and solutions

§1 Philosophy — The Ponytail Ladder

Stop at the first rung that holds:

  1. Does this need to exist at all? Speculative need = skip it. (YAGNI)
  2. Already in this codebase? Reuse existing patterns. Look before you write.
  3. Stdlib does it? Use it.
  4. Native platform feature covers it? <dialog> for modals, <details> for accordions, CSS over JS.
  5. Already-installed dependency solves it? Use it.
  6. Can it be one line? One line.
  7. Only then: the minimum code that works.

§2 Decision Framework

Can semantic HTML do it? → Yes → Done
No → Can 20 lines of vanilla CSS do it? → Yes → Write the CSS
No → Does a classless CSS framework solve it in one <link>? → Yes → Use it
No → Is the problem interactivity? → Use vanilla JS (or HTMX for complex cases)
No → OK, consider server-driven interactivity or declarative DOM behavior under 15KB

§3 File Structure Convention

  • Simple page: single index.html (embedded <style> in <head>, <script> before </body>)
  • Multi-section: index.html + style.css + script.js at root
  • Split files when they exceed ~400 lines
  • No src/ / dist/ unless there's a build step (YAGNI)
  • No package.json, no node_modules for static pages
  • Reference cookbook: Browse cookbook/ before writing new CSS from scratch

Minimal HTML Boilerplate

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Page Title</title>
  <style>/* tokens + styles here */</style>
</head>
<body>
  <main><!-- content --></main>
  <script>/* app logic here */</script>
</body>
</html>

§4 HTML Patterns

Semantic Elements (always prefer)

<nav>, <main>, <article>, <section>, <aside>, <header>, <footer>
<form>, <fieldset>, <legend>, <label>
<button>, <input>, <select>, <textarea>
<details>/<summary> for accordions (no JS needed)
<dialog> for modals (no JS needed for the element itself)
<picture>/<source> for responsive images

Choosing Overlay Primitives

NeedUseWhy
Modal (blocks page)<dialog>Focus trap, ::backdrop, ESC to close
Disclosure (optional visibility)<details>/<summary>No JS, semantic, SEO-friendly
Non-modal tooltip/menu[popover]Lightweight, no focus trap, positioning

Dialog Element

<dialog id="note-dialog">
  <form method="dialog">
    <h2>Edit Note</h2>
    <textarea></textarea>
    <button>Save</button>
    <button type="button" onclick="this.closest('dialog').close()">Cancel</button>
  </form>
</dialog>
<script>
  document.getElementById('note-dialog').showModal();
</script>

Details/Summary Accordion

<details>
  <summary>Common Question</summary>
  <p>Answer content here...</p>
</details>

Accessible Forms

<form>
  <label for="email">Email</label>
  <input type="email" id="email" autocomplete="email" inputmode="email" required>
  <label for="password">Password</label>
  <input type="password" id="password" autocomplete="current-password" required>
  <button type="submit">Login</button>
</form>

Prefer HTML5 native validation (required, pattern, type) for simple cases. Use JS validation only for cross-field rules, custom error messages, or server-side confirmation.

ARIA Attributes

<!-- Semantic HTML first — only add ARIA when HTML alone isn't enough -->
<button aria-expanded="false" aria-controls="menu" aria-label="Toggle menu">☰</button>
<nav aria-label="Main navigation" aria-current="page">
  <!-- nav items -->
</nav>

<!-- aria-labelledby references an existing element's text content -->
<h2 id="section-title">Settings</h2>
<div aria-labelledby="section-title">...</div>

<!-- Decorative images: alt="" + aria-hidden="true" -->
<img src="icon.svg" alt="" aria-hidden="true">
<img src="photo.jpg" alt="Description of image"> <!-- Meaningful images need alt text -->

Rules: Use semantic HTML first. role is rarely needed (buttons, links, navigation have implicit roles). Only add ARIA when native HTML semantics don't convey the meaning.

HTML Tables

<table>
  <thead>
    <tr><th>Name</th><th>Email</th><th>Role</th></tr>
  </thead>
  <tbody>
    <tr><td data-label="Name">Alex</td><td data-label="Email">[email protected]</td><td data-label="Role">Developer</td></tr>
    <tr><td data-label="Name">Jordan</td><td data-label="Email">[email protected]</td><td data-label="Role">Designer</td></tr>
  </tbody>
  <tfoot>
    <tr><td colspan="3">2 users total</td></tr>
  </tfoot>
</table>
/* Responsive table: stack on mobile */
@media (max-width: 600px) {
  thead { display: none; }
  tr { display: block; margin-bottom: 1rem; }
  td { display: block; text-align: right; }
  td::before { content: attr(data-label); float: left; font-weight: bold; }
}

Popover API (no JS needed for open/close)

<button popovertarget="my-popover">Open</button>
<div popover id="my-popover">Popover content</div>

Invoker Commands (commandfor)

<!-- Open dialog without JS -->
<button commandfor="my-dialog" command="showModal">Open Dialog</button>
<dialog id="my-dialog">Content</dialog>

<!-- Close dialog -->
<button commandfor="my-dialog" command="close">Close</button>

<!-- Toggle popover -->
<button commandfor="my-menu" command="togglepopover">Menu</button>
<div popover id="my-menu">Menu items</div>
  • Declarative open/close for <dialog> and <popover> without JavaScript
  • command="showModal" / command="close" for dialogs
  • command="togglepopover" / command="showpopover" / command="hidepopover" for popovers
  • Chrome 133+, limited browser support

Data Attributes for Dynamic Content

<a href="#" data-name="Twitter" style="--color: #1da1f2">🐦</a>
<a href="#" data-name="GitHub" style="--color: #333">💻</a>
a::before { content: attr(data-name); }

§5 CSS Patterns

CSS Custom Properties / Design Tokens

:root {
  --color-primary: oklch(54% 0.23 255);
  --color-bg: #ffffff;
  --color-text: #1a1a1a;
  --space-sm: 0.5rem;
  --space-md: 1rem;
  --space-lg: 2rem;
  --radius: 0.5rem;
  --font-body: system-ui, -apple-system, sans-serif;
}

/* Fallback syntax: var(--prop, fallback) */
.btn { background: var(--color-primary, blue); }

/* Scoped custom properties (override on specific elements) */
.card { --radius: 1rem; } /* only this card uses 1rem */

OKLCH Color System

:root {
  --lch-blue: 54% 0.23 255;
  --lch-blue-light: 95% 0.03 255;
  --lch-blue-dark: 80% 0.08 255;

  --color-link: oklch(var(--lch-blue));
  --color-selected: oklch(var(--lch-blue-light));
  --color-selected-dark: oklch(var(--lch-blue-dark));
  --color-link-50: oklch(var(--lch-blue) / 0.5);
}

Relative Colors from Syntax

:root {
  --color-primary: oklch(54% 0.23 255);
}
.btn {
  --btn-hover: oklch(from var(--color-primary) calc(l - 10%) c h);
  --btn-active: oklch(from var(--color-primary) calc(l - 20%) c h);
  background: var(--color-primary);
  &:hover { background: var(--btn-hover); }
  &:active { background: var(--btn-active); }
}

currentColor

.icon {
  color: var(--color-primary);
  filter: drop-shadow(0 0 4px currentColor);
}
.btn {
  color: var(--color-primary);
  border-color: currentColor;
  background: oklch(from currentColor l c h / 0.1);
}

CSS Position Reference

/* static — default, no offset */
.static { position: static; }

/* relative — offset from original position, space preserved */
.relative { position: relative; top: 10px; left: 20px; }

/* absolute — removed from flow, offset from nearest positioned ancestor */
.absolute { position: absolute; top: 0; right: 0; }

/* fixed — removed from flow, offset from viewport, stays on scroll */
.fixed { position: fixed; bottom: 20px; right: 20px; }

/* sticky — hybrid, sticks at threshold */
.sticky { position: sticky; top: 0; }

/* Stacking context: z-index only works on positioned elements */
.layer { position: relative; z-index: 10; }

CSS Nesting (no Sass needed)

.card {
  background: var(--color-bg);
  border-radius: var(--radius);

  & h2 { font-size: 1.5rem; }
  & p { color: var(--color-text); }
  &:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
}

@layer for Specificity Management

@layer tokens, base, components, utilities;

@layer tokens {
  :root { --color-primary: blue; }
}
@layer components {
  .btn { background: var(--color-primary); }
}
@layer utilities {
  .hidden { display: none; }
}
  • Use when you have 3+ style sources (tokens, base, components, utilities) that need specificity ordering

Fluid Typography with clamp()

h1 { font-size: clamp(1.5rem, 4vw + 1rem, 3rem); }
body { font-size: clamp(1rem, 0.9rem + 0.5vw, 1.125rem); }

text-wrap: balance

h1, h2, h3 { text-wrap: balance; }
/* Only works for six wrapped lines or fewer */
/* Fixes ugly line breaks on mobile headings */

Responsive Grid — No Media Queries

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.5rem;
}

:has() Parent Selector

.card:has(.badge) { border: 2px solid var(--color-primary); }
nav:has(.active) { background: var(--color-bg); }

:not() Pseudo-class

li:not(:last-child) { border-bottom: 1px solid #eee; }
input:not([type="submit"]) { border: 1px solid #ccc; }

Glassmorphism

.glass {
  background: rgba(255, 255, 255, 0.15);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 16px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}

Neumorphism

.neumorphic {
  background: #e0e0e0;
  border-radius: 12px;
  box-shadow: 8px 8px 15px #bebebe, -8px -8px 15px #ffffff;
}
.neumorphic:active {
  box-shadow: inset 4px 4px 8px #bebebe, inset -4px -4px 8px #ffffff;
}

Gradient Text

.gradient-text {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

Scroll Snapping Carousel

.carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  gap: 1rem;
}
.carousel-card {
  flex: 0 0 100%;
  scroll-snap-align: start;
}
@media (max-width: 600px) {
  .carousel-card { flex: 0 0 100%; }
}

::scroll-button() (CSS-Only Carousel Navigation)

.carousel {
  scroll-snap-type: x mandatory;
}
.carousel::scroll-button(inline-start) { content: "←"; }
.carousel::scroll-button(inline-end) { content: "→"; }
.carousel::scroll-marker-group {
  display: flex;
  gap: 0.5rem;
  justify-content: center;
}
.carousel::scroll-marker { content: ""; width: 10px; height: 10px; border-radius: 50%; }
.carousel::scroll-marker:target-current { background: var(--color-primary); }
/* Buttons auto-disable at scroll boundaries */
/* Clicking a scroll button scrolls ~85% of viewport */
  • Chrome 135+, no Firefox/Safari support yet

CSS-Only Dropdown with :checked

.dropdown { display: none; }
.dropdown-toggle:checked + .dropdown-label + .dropdown { display: block; }
/* Alternative with :focus-within */
.dropdown-wrapper:focus-within .dropdown { display: block; }

Popover API Styling

[popover] {
  padding: 1rem;
  border: 1px solid #ccc;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}

Anchor Positioning

.trigger { anchor-name: --my-trigger; }
.tooltip {
  position: fixed;
  position-anchor: --my-trigger;
  top: anchor(bottom);
  left: anchor(center);
  translate: -50% 8px;
}

@property for Animating Custom Properties

@property --angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}
.animated-border {
  --border-color: conic-gradient(from var(--angle), #667eea, #764ba2, #667eea); /* Used by ::before below */
  border: 2px solid transparent;
  animation: rotate-border 3s linear infinite;
}
@keyframes rotate-border {
  to { --angle: 360deg; }
}

Border Animations with Conic Gradients

.animated-border {
  position: relative;
  border-radius: 8px;
  overflow: hidden;
}
.animated-border::before {
  content: '';
  position: absolute;
  inset: -2px;
  background: conic-gradient(from var(--angle), #667eea, #764ba2, #667eea);
  border-radius: inherit;
  z-index: -1;
  animation: rotate-border 3s linear infinite;
}

Flexbox Properties Reference

.container {
  display: flex;

  /* Main axis direction */
  flex-direction: row | row-reverse | column | column-reverse;

  /* Main axis distribution */
  justify-content: flex-start | flex-end | center | space-between | space-around | space-evenly;

  /* Cross axis alignment */
  align-items: flex-start | flex-end | center | stretch | baseline;

  /* Multi-line wrapping */
  flex-wrap: nowrap | wrap | wrap-reverse;

  /* Cross axis alignment for multi-line */
  align-content: flex-start | flex-end | center | space-between | space-around;

  /* Gap between items */
  gap: 1rem;
}

.item {
  /* Shorthand: flex: grow shrink basis */
  flex: 1 1 auto;    /* grow equally, shrink equally, base size from content */
  flex: 0 0 200px;   /* no grow, no shrink, fixed 200px */
  flex: 1;           /* shorthand for flex: 1 1 0% */

  /* Override alignment for individual items */
  align-self: flex-start | flex-end | center | stretch;

  /* Reorder items (visual only, not DOM) */
  order: -1 | 0 | 1;
}

When to use Grid vs Flexbox: Grid for 2D layouts (rows AND columns). Flexbox for 1D layouts (row OR column). Grid: parent defines layout, children follow. Flexbox: children determine layout.

display: inline-flex

/* Flex container that flows inline with text */
.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.25rem 0.5rem;
  background: #eee;
  border-radius: 4px;
}
/* vs display: flex which creates a block-level container */

Flexbox Patterns

/* Isolate one item with margin: auto (navbar) */
nav ul { display: flex; gap: 1rem; }
nav ul li:first-child { margin-right: auto; }

/* Floating footer fix */
body {
  display: grid;
  grid-template-rows: auto 1fr auto;
  min-height: 100vh;
}

/* Equal columns */
.equal-cols { display: flex; gap: 1rem; }
.equal-cols > * { flex: 1; }

/* Increase clickable hitbox */
.clickable { display: flex; align-items: center; }
.clickable a { flex-grow: 1; }

/* 3-card layout: align buttons */
.card { display: flex; flex-direction: column; }
.card-content { flex: 1; }
.card button { margin-top: auto; }

Grid Patterns

/* Full-page dashboard with grid-template-areas */
.dashboard {
  display: grid;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
  grid-template-columns: 250px 1fr;
  grid-template-rows: auto 1fr auto;
  min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }

/* Stack elements (replaces position: absolute) */
.stack { display: grid; }
.stack > * { grid-area: 1 / 1; }

Resize Property

textarea { resize: both; }
textarea { resize: vertical; }
textarea { resize: none; }

Cubic Bezier Easing

/* Custom transition timing */
.btn { transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } /* Material ease */
.btn { transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); } /* Ease out */
.btn { transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); } /* Bounce */

Border-Image for Gradient Borders

.gradient-border {
  border: 3px solid;
  border-image: linear-gradient(135deg, #667eea, #764ba2) 1;
  /* The '1' slice value is critical — without it, corners break */
}

Animation & Transition Reference

/* Animation shorthand */
.element {
  animation: name duration timing-function delay iteration-count direction fill-mode;
  animation: fade-in 0.3s ease 0s 1 normal forwards;
}

/* Individual animation properties */
animation-name: fade-in;
animation-duration: 0.3s;
animation-timing-function: ease | linear | ease-in | ease-out | cubic-bezier(x, y, z, w);
animation-delay: 0s;
animation-iteration-count: 1 | infinite;
animation-direction: normal | reverse | alternate | alternate-reverse;
animation-fill-mode: none | forwards | backwards | both;

/* Transition shorthand */
.element {
  transition: property duration timing-function delay;
  transition: all 0.3s ease 0s;
  transition: background 0.2s ease, transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Individual transition properties */
transition-property: all | background | transform | opacity;
transition-duration: 0.3s;
transition-timing-function: ease;
transition-delay: 0s;

@starting-style for Entry Animations

dialog {
  opacity: 1;
  transform: scale(1);
  transition: opacity 0.3s, transform 0.3s, display 0.3s allow-discrete;

  @starting-style {
    opacity: 0;
    transform: scale(0.95);
  }
}

light-dark() for Theme Switching

:root { color-scheme: light dark; }
body {
  background: light-dark(#ffffff, #121212);
  color: light-dark(#1a1a1a, #e0e0e0);
}

aspect-ratio

/* Consistent proportions without padding hacks */
.video { aspect-ratio: 16 / 9; }
.square { aspect-ratio: 1; }
.portrait { aspect-ratio: 3 / 4; }

/* Responsive: maintain ratio while filling container */
.card img { aspect-ratio: 4 / 3; width: 100%; object-fit: cover; }

min() and max() Functions

/* Responsive width: never exceed 800px, shrink on small screens */
.container { width: min(800px, 90%); }

/* Minimum size: never go below 250px */
.sidebar { width: max(250px, 25%); }

/* Responsive padding */
.section { padding: min(5rem, 8vw); }

Columns (Masonry Layout)

/* One-line masonry — no grid or JS needed */
.gallery {
  columns: 3;
  column-gap: 1rem;
}
.gallery-item {
  break-inside: avoid;
  margin-bottom: 1rem;
}

/* Responsive masonry */
@media (max-width: 900px) { .gallery { columns: 2; } }
@media (max-width: 600px) { .gallery { columns: 1; } }

Use light-dark() for auto-only theme (respects OS). Use data-theme attribute toggle when user can override.

Container Queries

.card-container { container-type: inline-size; }
@container (min-width: 400px) {
  .card { display: grid; grid-template-columns: 1fr 2fr; }
}

CSS Shorthand vs Longhand

/* Shorthand — use when clear */
margin: 1rem 2rem;
background: #fff url('img.png') no-repeat center / cover;

/* Longhand — use when you need individual control */
margin-top: 1rem;

CSS @function (Custom Functions)

/* Define a custom function */
@function --transparent(--color, --alpha: 0.2) {
  result: oklch(from var(--color) l c h / var(--alpha));
}

/* Typed parameters */
@function --inner-radius(--outer, --padding) {
  result: max(0px, var(--outer) - var(--padding));
}

/* Use the function */
.card {
  background: --transparent(var(--color-primary), 0.1);
  border: 1px solid --transparent(var(--color-primary), 0.2);
}
  • Parameters start with -- like custom properties
  • result defines the return value (not return like JS)
  • Last result wins (CSS cascade behavior)
  • Can include media queries inside functions for responsive logic
  • Browser support: limited (Chrome 133+, not production-ready yet)

CSS Subgrid

/* Parent grid defines rows */
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto 1fr auto;
  gap: 1rem;
}

/* Child inherits parent's row tracks */
.card {
  grid-row: span 3;
  grid-template-rows: subgrid;
}

/* Override gap on the card itself (gap is a container property) */
.card { gap: 0; }
  • Children inherit parent grid tracks instead of creating their own
  • Solves the "misaligned buttons in cards" problem
  • Use span N instead of hardcoded line numbers for responsive grids
  • Gap inheritance: parent gap applies to subgrid items too

View Transitions API

/* Animate between page states */
::view-transition-old(root) {
  animation: fade-out 0.3s ease;
}
::view-transition-new(root) {
  animation: fade-in 0.3s ease;
}

/* Named transitions for specific elements */
.hero-title {
  view-transition-name: hero;
}
// Trigger transition
document.startViewTransition(() => {
  updateDOM(); // Your DOM update
});
  • Animates between page states without a full page reload
  • Works with SPA navigation and MPA with @view-transition
  • Chrome 111+, Firefox behind flag, Safari 18+

sibling-index() and sibling-count()

Warning: No widespread browser support yet. Use CSS counters or JavaScript for equivalent functionality in production.

/* Style based on element's position among siblings */
li {
  opacity: calc(1 - (sibling-index() - 1) * 0.1);
}

/* Create numbered backgrounds */
li::before {
  content: sibling-index();
}

/* Calculate values based on total siblings */
.item {
  width: calc(100% / sibling-count());
}
  • sibling-index() — 1-based position among siblings
  • sibling-count() — total number of siblings
  • Replaces manual counter/JS solutions for nth-child calculations

corner-shape

/* Control corner shape */
.card {
  border-radius: 1rem;
  corner-shape: round; /* default */
}

/* Superellipse (squircle) corners */
.card {
  border-radius: 1rem;
  corner-shape: superellipse;
}
  • New CSS property for controlling corner curvature
  • round (default), superellipse, bevel
  • Creates iOS-style squircle corners natively
  • Chrome 137+, experimental

Position Fallbacks (@position-try)

/* Define fallback positions */
@position-try --below-right {
  inset: unset;
  top: anchor(bottom);
  left: anchor(left);
}

@position-try --below-left {
  inset: unset;
  top: anchor(bottom);
  right: anchor(right);
}

/* Menu tries positions in order */
.menu {
  position: absolute;
  position-anchor: --my-button;
  position-area: bottom right;
  position-try-fallbacks: --below-right, --below-left;
}
  • CSS-only intelligent positioning without JavaScript
  • Browser tries each fallback until one fits in viewport
  • Works with anchor positioning for dropdowns, tooltips, popovers
  • Chrome 129+, limited Firefox/Safari support

Intersection Observer (Scroll Animations)

// Observe elements entering viewport
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add('visible');
    } else {
      entry.target.classList.remove('visible');
    }
  });
}, { threshold: 0.1 });

// Observe all elements
document.querySelectorAll('.animate').forEach(el => observer.observe(el));
.animate {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.5s, transform 0.5s;
}
.animate.visible {
  opacity: 1;
  transform: translateY(0);
}
  • Detects when elements enter/leave viewport
  • Better than scroll events (no performance issues)
  • Use for: scroll animations, lazy loading, infinite scroll

Scroll-Driven Animations (CSS-only)

/* Reveal elements on scroll */
@keyframes fade-in {
  from { opacity: 0; transform: translateY(30px); }
  to { opacity: 1; transform: translateY(0); }
}

.reveal {
  animation: fade-in linear both;
  animation-timeline: view();
  animation-range: entry 0% cover 40%;
}

/* Progress bar */
.progress {
  animation: grow-width linear;
  animation-timeline: scroll();
}

@keyframes grow-width {
  from { width: 0%; }
  to { width: 100%; }
}
  • animation-timeline: view() — tied to element visibility
  • animation-timeline: scroll() — tied to page scroll
  • No JavaScript needed for scroll-triggered animations

color-mix()

/* Tint/shade without manual color math */
.btn-hover { background: color-mix(in oklch, var(--primary), black 15%); }
.subtle { background: color-mix(in oklch, var(--bg), var(--text) 5%); }
.border { border-color: color-mix(in oklch, var(--primary), transparent 50%); }

@scope (Scoped Styling)

/* Style only within this component's subtree */
@scope (.card) to (.card-footer) {
  p { color: var(--text); }
  a { color: var(--primary); }
}
  • Prevents style leakage to siblings or parents
  • The to clause limits scope to a boundary element
  • Chrome 118+, Firefox 128+, Safari 17.4+ (partial)

<template> Element

<template id="card-template">
  <article class="card">
    <h3></h3>
    <p></p>
  </article>
</template>
const template = document.getElementById('card-template');
const clone = template.content.cloneNode(true);
clone.querySelector('h3').textContent = item.title;
container.appendChild(clone);
  • Efficient DOM cloning without re-parsing HTML
  • Content not rendered until added to DOM
  • Use <template> + cloneNode for reusable DOM fragments with event listeners. Use template literals for simple list rendering into innerHTML.

§6 JavaScript Patterns

Vanilla DOM Manipulation

const el = document.querySelector('.my-element');
const all = document.querySelectorAll('.items');

const div = document.createElement('div');
div.textContent = 'Hello';
document.body.appendChild(div);

el.addEventListener('click', (e) => {
  e.preventDefault();
  // handle
});

localStorage Persistence

const saveData = (key, data) => localStorage.setItem(key, JSON.stringify(data));
const loadData = (key, fallback = []) => {
  try { return JSON.parse(localStorage.getItem(key)) || fallback; }
  catch { return fallback; }
};

// Theme persistence
const saveTheme = (theme) => localStorage.setItem('theme', theme);
const loadTheme = () => localStorage.getItem('theme') || 'light';

Dark Mode Toggle

const toggle = document.getElementById('dark-toggle');
toggle.addEventListener('click', () => {
  const isDark = document.documentElement.toggleAttribute('data-theme', 'dark');
  saveTheme(isDark ? 'dark' : 'light');
  toggle.textContent = isDark ? '☀️' : '🌙';
});
if (loadTheme() === 'dark') {
  document.documentElement.setAttribute('data-theme', 'dark');
  toggle.textContent = '☀️';
}

Accessible Navbar with ARIA

const menuBtn = document.querySelector('[aria-label="Toggle menu"]');
const nav = document.querySelector('nav');

menuBtn.addEventListener('click', () => {
  const expanded = menuBtn.getAttribute('aria-expanded') === 'true';
  menuBtn.setAttribute('aria-expanded', String(!expanded));
  nav.setAttribute('aria-hidden', String(expanded));
});

document.addEventListener('click', (e) => {
  if (!nav.contains(e.target) && !menuBtn.contains(e.target)) {
    menuBtn.setAttribute('aria-expanded', 'false');
    nav.setAttribute('aria-hidden', 'true');
  }
});

Hash Routing

function navigate(hash) { window.location.hash = hash; }

window.addEventListener('hashchange', () => {
  const route = window.location.hash.slice(1) || '/';
  document.querySelectorAll('[data-route]').forEach(el => {
    el.hidden = el.dataset.route !== route;
  });
});

Template Literals for Dynamic HTML

const renderItem = (item) => `
  <article class="item" data-id="${item.id}">
    <h3>${item.title}</h3>
    <p>${item.content}</p>
    <button class="edit-btn">Edit</button>
    <button class="delete-btn">Delete</button>
  </article>
`;

container.innerHTML = items.map(renderItem).join('');

ES Modules

<script type="module" src="app.js"></script>
import { saveData, loadData } from './storage.js';
import { renderItems } from './ui.js';

Form Validation

form.addEventListener('submit', (e) => {
  e.preventDefault();
  const email = form.querySelector('#email');
  const password = form.querySelector('#password');

  form.querySelectorAll('.error').forEach(el => el.remove());

  if (!email.value.includes('@')) {
    email.insertAdjacentHTML('afterend', '<span class="error">Invalid email</span>');
    return;
  }
  if (password.value.length < 8) {
    password.insertAdjacentHTML('afterend', '<span class="error">Min 8 characters</span>');
    return;
  }
  // Submit...
});

Async/Await with Fetch

// Fetch API with async/await
async function loadData(url) {
  try {
    const response = await fetch(url);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return await response.json();
  } catch (error) {
    console.error('Fetch failed:', error);
    return null;
  }
}

// Usage
const data = await loadData('https://api.example.com/data');

Promises (Quick Reference)

// Create a promise
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// Chain promises
fetchUser()
  .then(user => fetchPosts(user.id))
  .then(posts => renderPosts(posts))
  .catch(error => showError(error));

// Parallel execution — note: returns Response objects, not parsed data
const [usersRes, postsRes] = await Promise.all([
  fetch('/api/users'),
  fetch('/api/posts')
]);
const [users, posts] = await Promise.all([usersRes.json(), postsRes.json()]);

Event Delegation

// Handle events on parent, delegate to children
document.querySelector('.list').addEventListener('click', (e) => {
  const item = e.target.closest('.item');
  if (!item) return;

  if (e.target.matches('.delete-btn')) {
    item.remove();
  } else if (e.target.matches('.edit-btn')) {
    editItem(item.dataset.id);
  }
});

AbortController (Cancel Fetches)

const controller = new AbortController();

fetch('/api/data', { signal: controller.signal })
  .then(res => res.json())
  .catch(err => {
    if (err.name === 'AbortError') console.log('Cancelled');
  });

// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);

structuredClone() (Deep Clone)

// Deep clone objects (replaces JSON roundtrip)
const original = { nested: { value: 42 } };
const clone = structuredClone(original);

// Works with Dates, Maps, Sets, TypedArrays
const complex = { date: new Date(), map: new Map([['key', 'value']]) };
const deepClone = structuredClone(complex);

crypto.randomUUID()

// Generate unique IDs without libraries
const id = crypto.randomUUID();
// "3b241101-e2bb-4d7a-8702-9e0e0a3f0f87"

// Useful for keys in dynamic lists
const item = { id: crypto.randomUUID(), text: 'New item' };

Array Iteration Methods

// map — transform each element, return new array
const names = users.map(u => u.name);

// filter — keep elements that pass a test
const adults = users.filter(u => u.age >= 18);

// reduce — accumulate into a single value
const total = prices.reduce((sum, p) => sum + p, 0);

// find — get first matching element
const admin = users.find(u => u.role === 'admin');

// findIndex — get index of first match
const idx = users.findIndex(u => u.id === targetId);

// forEach — run code for each element (no return value)
users.forEach(u => console.log(u.name));

// Chaining
const result = users
  .filter(u => u.active)
  .map(u => u.name)
  .join(', ');

Destructuring

// Object destructuring
const { name, age, email = 'N/A' } = user;

// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first=1, second=2, rest=[3,4,5]

// In function parameters
function greet({ name, greeting = 'Hello' }) {
  return `${greeting}, ${name}`;
}
greet({ name: 'Alex' }); // "Hello, Alex"

Optional Chaining & Nullish Coalescing

// Optional chaining (?.) — safe property access
const city = user?.address?.city; // undefined if any part is null/undefined
const first = arr?.[0]; // undefined if arr is null/undefined
const result = obj?.method?.(); // undefined if method doesn't exist

// Nullish coalescing (??) — fallback only for null/undefined
const name = user.name ?? 'Anonymous';
// vs || which also falls back for 0, '', false

DOMContentLoaded

// Run code after HTML is parsed, before images/styles
document.addEventListener('DOMContentLoaded', () => {
  // Safe to query DOM elements here
  const btn = document.querySelector('#my-btn');
});

§7 CSS Framework Allowlist

Eligibility criteria (all must pass):

  • Loaded via single <link> tag from CDN (or minimal setup)
  • No JavaScript dependency (or minimal vanilla JS only)
  • No build step / no Node/npm required for basic usage
  • Semantic HTML-first (minimal or zero class names preferred)
  • MIT or permissive license
  • < ~60KB gzipped (exceptions for comprehensive component libraries)
  • Works in modern evergreen browsers

Category A: Classless (zero classes, pure semantic HTML)

FrameworkSizeBest for
Pico CSS~14KBMost complete; classless version; 20 themes; dark/light
EdibleCSS~3.8KBWCAG AA, truly zero classes
Acorn CSS~3.8KBGenerative design from 5 variables
The New CSS~5KBAddon system; modular; fast
Simple.css~6KBTruly minimal classless
MVP.css~8KBQuick prototypes
Sakura~8KBMinimal, pretty defaults
Tacit~5KBClassless for any project
Holiday.css~5KBModern aesthetic, nav/dropdowns
new.css~4KBClassic classless

Category B: shadcn-aesthetic (no React needed)

FrameworkSizeBest forNotes
Basecoat UI~19KB JSPreferred — shadcn v4 port, CDN, minimal vanilla JSUser's top pick. Tailwind CSS v4 + plain HTML. Works via CDN without build.
slick-css~5.7KBshadcn style via attribute selectors; tinyZero classes, attribute-based
shadcn-classless~15KBZero-class, dialog/nav/accordion supportPure CSS
Daft CSS~55KBClosest to shadcn visuals; zero JS; modern CSS onlyHeaviest but most complete

Category C: Minimal-class component frameworks

FrameworkSizeBest for
µCSS~19KB20+ themes, button/form/nav components
Aesthetium~12KBAttribute-based variants; clean OpenAI-style
Spectre.css~10KBLightweight component framework
UIkit~12KBModular, responsive, good docs

Category D: Tailwind-based (CDN-loadable, no build step)

FrameworkSizeBest forNotes
Flowbite~15KB JSInteractive components via data attributesRequires Tailwind CSS. CDN available. MIT.
DaisyUI~48KB CSSMost Tailwind componentsRequires Tailwind. CDN via Play or direct.

Category E: Effect libraries (add-ons)

FrameworkSizeBest for
GlassKit~6.5KBPure CSS glassmorphism component library
signature-css-framework~15KBGlassmorphism + RTL support

Allowed JS Enhancement

  • HTMX (~14KB) from CDN — for AJAX, polling, form validation, search

Framework Selection Guide

Need a quick polished page with zero CSS?
→ Pico CSS (classless) or Basecoat UI (shadcn aesthetic)

Need interactive components (modals, dropdowns, toasts)?
→ Basecoat UI or Flowbite (both have JS for interactivity)

Building a content-heavy site (blog, docs)?
→ Pico CSS or Simple.css (classless, semantic)

Building a dashboard or app UI?
→ Basecoat UI or UIkit (component-rich)

Want shadcn aesthetics without React?
→ Basecoat UI (preferred) or slick-css (minimal)

Documentation Access for Agents

Always research before using a framework. See §12 for full MCP tool documentation.

Quick reference:

  1. Context7 MCP (preferred) — mcp__context7__resolve-library-id + mcp__context7__query-docs
  2. Exa MCPmcp__exa__web_search_exa for technique research
  3. Direct fetchWebFetch(url) as fallback
  4. CDN links — Reference the CDN URL in code examples for immediate usage

Example Context7 workflow:

User: "How do I use Basecoat UI's button component?"
Agent: mcp__context7__resolve-library-id("basecoat ui") → mcp__context7__query-docs("/components/button")

§8 Anti-Patterns (What NOT to Do)

Don'tInstead
npm init / package.json for static HTMLJust write HTML files
Tailwind CSS for simple webpagesPlain CSS or a classless framework (§7)
BootstrapSemantic HTML + custom CSS (§4, §5)
CSS preprocessor (Sass/PostCSS)Native CSS nesting (§5)
Build tool (Webpack/Vite/Parcel)No build step — files served directly
Premature file splittingKeep files until they exceed ~400 lines
Component abstraction for one instanceOnly abstract at 5+ repeated instances
TypeScript for simple pagesPlain JavaScript (§6)
src/ / dist/ folder "for later"Root-level files until there's a need
Utility-first CSS for single-page sitesSemantic CSS with custom properties
BEM naming (.card__title--large)Simple selectors (.card-title)
<script> in <head> without defer<script defer> or place before </body>
document.write()DOM manipulation methods (§6)
var in JSconst / let only
setTimeout for animationsCSS transitions/animations or requestAnimationFrame
innerHTML with user inputtextContent or sanitize with DOMPurify
onclick in template literalsEvent delegation with data-id attributes (§6)

§9 Design Quick Reference

PropertyValue
Spacing scale4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80, 96 (4px base)
Body font16px, line-height 1.5, max 4 font sizes
Touch targetsMin 44×44px, recommended 48×48px
Contrast4.5:1 normal text, 3:1 large text, 3:1 UI components (WCAG AA)
Button hierarchyPrimary (solid, ONE per view), Secondary (outlined), Tertiary (text-only)
Dark modebg #121212, text #E0E0E0, reduce saturation
Form labelsAlways above inputs, never placeholders as labels

§10 Accessibility Minimums

  • Semantic HTML = most accessibility for free
  • :focus-visible rings on all interactive elements
  • prefers-reduced-motion guard on all animations
  • Color never the sole indicator (add icons/text labels)
  • Form inputs: always <label>, autocomplete, inputmode
  • ARIA attributes updated via JavaScript when state changes
  • <dialog> for modals (handles focus trap natively)
  • Test with voiceover/narrator before shipping
/* Focus visible ring */
:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

/* Reduced motion guard */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

§10b Additional Techniques (from Video Transcripts)

align-content on Block Elements (Chrome 123+)

/* Center vertically without flexbox/grid */
.center-block {
  text-align: center;
  align-content: center;
  min-height: 200px;
}

Customizable Select (appearance: base-select)

/* Opt into new select model */
select {
  appearance: base-select;
}

/* Style the picker dropdown */
select::picker {
  appearance: base-select;
  background: white;
  border: 1px solid #ccc;
  border-radius: 8px;
}

/* Rotate arrow when open */
select:open::picker-icon {
  transform: rotate(180deg);
  transition: transform 0.3s;
}

/* Style the checkmark */
option::checkmark {
  content: '✓';
  color: green;
}
  • Chrome 134+, limited Firefox/Safari support
  • Falls back to native select styling in unsupported browsers

display: contents

/* Remove element from layout, children become direct children of parent */
.wrapper {
  display: contents;
}
  • Useful for maintaining flex/grid layouts with wrapper elements
  • Wrapper disappears from layout flow, children inherit parent's grid

CSS Counters for Numbering

/* Auto-number headings */
:root { counter-reset: heading; }
h2 { counter-increment: heading; }
h2::before {
  content: counter(heading) ". ";
  font-weight: bold;
}

:popover-open Pseudo-class

/* Style popover only when open */
.menu:popover-open {
  display: grid;
}
  • Browser toggles display automatically — don't set display manually

Inert Attribute (Accessibility)

<!-- Make element non-interactive and invisible to assistive tech -->
<div inert>
  <button>This button is unreachable</button>
</div>
  • Prevents focus and click events on disabled content
  • Useful for off-screen menus, background content during modals

Skip Link (Accessibility)

<a href="#main" class="skip-link">Skip to content</a>
<main id="main">...</main>
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  padding: 8px;
  background: #000;
  color: white;
  z-index: 100;
  transition: top 0.3s;
}
.skip-link:focus { top: 0; }

Glow Effect with Box-Shadow

.glow {
  box-shadow: 40px 0 100px blue, -40px 0 100px purple;
}

Gradient Fill on Hover

.btn {
  position: relative;
  z-index: 0;
}
.btn::after {
  content: '';
  position: absolute;
  inset: -3px;
  background: linear-gradient(135deg, #667eea, #764ba2);
  z-index: -1;
  border-radius: inherit;
}
.btn:hover::after { z-index: 0; }

Ternary Operator (JS Pattern)

// Concise conditional
const result = condition ? valueIfTrue : valueIfFalse;

// In event handlers
darkMode !== 'active' ? enableDark() : disableDark();

Early Return Pattern (JS)

// Guard clauses — exit early, keep happy path flat
function processUser(user) {
  if (!user) return;
  if (!user.loggedIn) return;
  if (!user.email) return;
  // happy path here
}

Spread Operator for Immutable Copies

// Object copy (breaks reference)
const copy = { ...original };

// Override specific property
const updated = { ...product, price: product.price * 0.5 };

// Array copy
const newArr = [...oldArr];

§11 Cookbook Reference

Patterns live in cookbook/ — each is a standalone HTML file with embedded CSS. Browse before writing new CSS from scratch.

Layouts

  • cookbook/layouts/responsive-grid/ — Auto-fit grid, no media queries
  • cookbook/layouts/sidebar-layout/ — Sidebar + main content
  • cookbook/layouts/hero-section/ — Full-width hero with content
  • cookbook/layouts/holy-grail/ — Classic header/sidebar/main/footer
  • cookbook/layouts/dashboard/ — grid-template-areas dashboard
  • cookbook/layouts/masonry/ — Pinterest-style masonry grid

Navigation

  • cookbook/navigation/navbar/ — Flexbox navbar with margin:auto isolation
  • cookbook/navigation/sidebar-menu/ — Animated dropdowns, CSS grid
  • cookbook/navigation/mobile-hamburger/ — ARIA accessible toggle
  • cookbook/navigation/sticky-header/ — position: sticky header
  • cookbook/navigation/breadcrumbs/ — Semantic breadcrumb trail

Forms

  • cookbook/forms/login-signup/ — Form validation, responsive
  • cookbook/forms/form-validation/ — Front-end validation patterns
  • cookbook/forms/search-bar/ — Search input with icon
  • cookbook/forms/contact-form/ — Multi-field contact form
  • cookbook/forms/multi-step/ — Step-by-step form wizard

Cards

  • cookbook/cards/profile-card/ — User profile card
  • cookbook/cards/product-card/ — E-commerce product card
  • cookbook/cards/article-card/ — Blog article preview
  • cookbook/cards/pricing-card/ — Pricing tier comparison

Effects

  • cookbook/effects/glassmorphism/ — backdrop-filter: blur + rgba
  • cookbook/effects/neumorphism/ — Dual box-shadows
  • cookbook/effects/gradient-text/ — background-clip: text
  • cookbook/effects/border-animations/ — @property + conic-gradient
  • cookbook/effects/hover-effects/ — Transform and transition effects
  • cookbook/effects/scroll-snap/ — CSS-only carousel
  • cookbook/effects/scroll-driven-animations/ — animation-timeline: view()
  • cookbook/effects/animated-tooltips/ — ::before/::after with attr()
  • cookbook/effects/triangles/ — Border trick for CSS shapes

Interactivity

  • cookbook/interactivity/dark-mode/ — CSS vars + localStorage + prefers-color-scheme
  • cookbook/interactivity/todo-app/ — localStorage CRUD
  • cookbook/interactivity/modal-dialog/<dialog> element
  • cookbook/interactivity/accordion/<details>/<summary>
  • cookbook/interactivity/carousel/ — Scroll-snap + JS navigation
  • cookbook/interactivity/tabs/ — :checked pseudo-class
  • cookbook/interactivity/toast-notifications/ — Auto-dismiss notifications
  • cookbook/interactivity/popover-api/ — HTML popover attribute
  • cookbook/interactivity/css-only-dropdown/ — :focus/:focus-within

Typography

  • cookbook/typography/fluid-type/ — clamp() for responsive type
  • cookbook/typography/text-balance/ — text-wrap: balance
  • cookbook/typography/decorative-headings/ — Gradient text, writing-mode
  • cookbook/typography/vertical-rhythm/ — Consistent spacing baseline

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.