Capybara Weekly
The world's chillest rodent, delivered to your inbox every Thursday.
SubscribeEvery box below is alive. Drag a slider, flip a switch, and watch both the result and the code change together. That is the whole trick to learning front-end: stop reading about it, start poking it.
The whole web is three files talking to each other. HTML says what exists. CSS says what it looks like. JavaScript says what happens when you touch it. Nothing else you learn later changes this.
Headings, buttons, images, lists. Structure and meaning — no styling opinions.
Color, size, spacing, layout, motion. It decorates the nouns HTML created.
Click, type, fetch, update. It reacts to humans and changes the other two live.
Turn the knobs and watch one little card get built up, layer by layer:
Nothing on a webpage is a free-floating shape. Every element is a rectangle with four layers: content, then padding (inside space), then border, then margin (outside space). Ninety percent of "why is this thing in the wrong place" is a padding/margin question.
Almost every website is the same five regions. A header at the top, a nav to get around, the main content, an optional aside for secondary stuff, and a footer at the bottom. Learn this skeleton once and you can read the source of any site on the web.
Logo, site title, search, sign-in. Usually identical on every page.
The main set of links. Screen readers offer to jump straight to it.
Exactly one per page, and it holds the content that is unique to this page.
Related links, filters, adverts. Useful, but not the main story.
Contact details, copyright, sitemap, social links.
Each lesson on this page is a <section>. Give it a heading and it means something.
<div> for everything? Because a div means
nothing. These named regions are called landmarks: a blind visitor can jump straight
to "main", Google understands your page better, and reader modes work. They cost you nothing —
they're the same box, with a name.
You have seen these eight things a thousand times. Navbar, hero, columns, card grid, accordion, tabs, breadcrumb, footer. Almost every page you will ever build is a stack of these. Pick one and it gets built in front of you, with the code that made it.
Most page layouts are just "how do I split this width?". There are only three answers you need, and you can see all three below.
You know you want 3. grid-template-columns: repeat(3, 1fr).
You don't care how many. repeat(auto-fit, minmax(220px, 1fr)).
One fixed, one flexible. grid-template-columns: 260px 1fr.
<details> —
no JavaScript, keyboard accessible, and findable with Ctrl+F. The tabs use radio buttons and
:checked, so they need no JavaScript either. Reach for the platform first.
Flexbox lays boxes out along one axis. You pick a direction (a row or a column), then say how the leftover space should be shared. Navbars, toolbars, button groups, centring anything — that's flexbox. It's the single most-used layout tool on the web.
display:flex; justify-content:center; align-items:center;
centres anything, horizontally and vertically. This used to be a famously hard problem.
Grid is flexbox's two-dimensional sibling. You define columns and rows up front, then drop items into the cells. Page layouts, dashboards, photo galleries, magazine spreads. Rule of thumb: flexbox for a line of things, grid for a layout.
fr means "one share of the free space". And
repeat(auto-fit, minmax(200px, 1fr)) builds a gallery that reflows itself at
every screen size — with no media queries at all. Try the auto-fit button, then
make your browser window narrower.
You don't build a mobile site and a desktop site. You build one page that bends. Two ideas do most of the work: fluid units that scale continuously, and media queries that snap to a different layout past a certain width.
clamp(), so it slides smoothly instead of jumping.
width:50% — half of whatever contains it.
The safe unit for text and spacing. Respects the user's browser font setting.
100dvh = full viewport height, correctly, even with mobile browser bars.
clamp(1rem, 4vw, 3rem) — grows with the screen but never gets silly.
This is the big shift of the last few years. A media query asks "how wide is the screen?" A container query asks "how wide is the space I was dropped into?" That means one component can be genuinely reusable: put it in a sidebar and it goes compact, put it in a main column and it goes wide — no page-level knowledge required.
The world's chillest rodent, delivered to your inbox every Thursday.
SubscribeBefore you install a component library, check what's already built in. Modern browsers ship sliders, colour pickers, date pickers, search boxes, switches and progress bars — all accessible, keyboard-friendly and translated into every language, at zero cost. Below is a working control panel using nothing but native HTML.
<input> elements, they
already work with the Tab key, screen readers, voice control and browser autofill. A
<div> pretending to be a slider does none of that. Try tabbing through the
panel above with your keyboard.
Modals used to take 200 lines of JavaScript. Now <dialog>,
the popover attribute and <details> handle focus trapping,
Escape-to-close, click-outside and stacking for you. This is some of the newest, most
useful stuff in the platform.
<details> + <summary> is a native show/hide.
It's searchable by Ctrl+F, it's keyboard accessible, and it animates now that
browsers support interpolate-size. FAQ sections, done.
Yes — details[open] lets you restyle the open state, and
::details-content lets you animate the reveal.
Rendered in the browser's top layer, so I'm never trapped behind
overflow:hidden. Click anywhere else to dismiss me.
The browser is a validation engine you already own. Mark a field
required, give it type="email" or a pattern, and CSS
can style the valid/invalid states with :user-valid — which politely waits
until the user has actually finished typing before shouting at them.
:user-valid exists.
Visual polish is mostly four properties. Gradients for colour interest,
box-shadow for depth, backdrop-filter for that frosted-glass
look, and border-radius for softness. Turn the knobs until it looks like an
app you'd want to use.
Every app UI is this, repeated.
A transition is CSS saying "don't jump — glide." You change a value, and the browser fills in every frame between the old and new. Timing and easing are what separate "cheap" from "expensive-feeling". Under ~150ms feels twitchy; over ~500ms feels sluggish.
transform and opacity. The GPU
handles those without re-doing layout. Animating width, top or
margin forces the browser to recalculate the whole page every frame — that's
where jank comes from.
Scroll position is now an animation timeline. Things that used to need a JavaScript library — progress bars, reveal-on-scroll, parallax, scroll-snapping carousels — are CSS one-liners in modern browsers, and they run on the compositor so they never stutter.
1. Scroll progress. That bar pinned to the top of your window is
animation-timeline: scroll() — three lines of CSS, no JS.
2. Reveal on scroll. These cards fade and rise as they enter the viewport, driven by
animation-timeline: view():
Animation is tied to how far into view the element is — not to a timer.
Runs off the main thread, so it stays at 60fps while JavaScript is busy.
All of it is disabled automatically if the visitor asked for reduced motion.
3. Scroll snap. Drag this sideways — it locks neatly to each panel, again pure CSS:
JavaScript is mostly waiting. You tell the browser "when this happens to that element, run this function". Clicks, typing, hovering, scrolling, resizing — every interactive thing you've ever used is that one pattern, repeated.
This is the idea that everything modern is built on. Don't poke at the page
directly. Keep a plain object — the state — that describes what's true right now.
Change the state, and let one render() function redraw from it. React, Vue,
Svelte and Flutter are all elaborate versions of this single sentence.
Everything above, in one working thing. Grid layout, native inputs, events, state, transitions and derived data (that filter and counter aren't stored — they're computed from state every render). About 40 lines of JavaScript.
Nothing here yet. Add something above. ↑
todos array and calls
render(). No button knows what the screen looks like. That separation is
the entire reason front-end frameworks exist.
If you learned CSS more than a couple of years ago, this section is the update. Each of these replaced something that used to require JavaScript, a build step, or a horrible hack.
Style an element based on what's inside it. Impossible for 20 years.
Nest rules natively. One less build tool in your project.
Type and spacing that scale smoothly without a single breakpoint.
Blend two colours in CSS — hover states derived from one brand colour.
Lightness that actually looks equal across hues, plus wider-gamut screens.
Decide which stylesheet wins, on purpose, instead of fighting specificity.
Cross-fade or morph between states and pages — app feel, zero framework.
Group selectors readably and control specificity deliberately.
:has() — the parent selectorTick a card. The card restyles itself because it has a checked input inside it — and the grid restyles because it has a selected card inside it. No JavaScript is running here at all.
/* pure CSS — the parent reacts to its child */ .has-card:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); } .has-grid:has(input:checked) { background: var(--accent-soft); } .has-card:not(:has(input:checked)) { opacity: .55; }
Click to reorder. The browser takes a snapshot before and
after, then animates every item to its new home automatically. This is
document.startViewTransition(), and it works across full page navigations too.
clamp()One line replaces four breakpoints. Drag to resize the box — the heading scales continuously between a floor and a ceiling.
display:grid; place-items:center;
display:grid; grid-template-columns: repeat(auto-fit,minmax(220px,1fr));
display:flex; justify-content:space-between; align-items:center;
font-size:clamp(1rem,2.5vw,2rem);
transition:transform .2s ease; &:hover{transform:translateY(-3px)}
@media (prefers-reduced-motion:reduce){ *{animation:none!important} }
<button> for actions, <a> for navigation. Accessibility mostly comes free if you do.min-width queries.transform and opacity only. Everything else risks jank.This whole site is a single index.html with no build step, so deploying is genuinely a drag-and-drop:
index.html in a folder on its own.*.pages.dev URL with HTTPS and a global CDN.npx wrangler pages deploy . from inside the folder.