PXT Tech Studio Back to the studio
Interactive · No install · No prior code

The modern web, one knob at a time.

Every 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.

00Three languages, three jobs

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.

HTML

The nouns

Headings, buttons, images, lists. Structure and meaning — no styling opinions.

CSS

The adjectives

Color, size, spacing, layout, motion. It decorates the nouns HTML created.

JavaScript

The verbs

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:

Layers

Turn CSS off to see what a webpage looks like naked. Every site is that, underneath.
Result

  

01Everything on a page is a box

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.

Box model

Result
content

  
Pro tip: padding grows the box inwards from the edge and is clickable/coloured; margin is invisible space that pushes other boxes away. When two vertical margins meet, they "collapse" into the bigger one — a classic beginner surprise.

02The anatomy of a page

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.

Regions

Shape

Flip that last switch: the page looks identical, but the meaning is gone.
Result
header — logo, title, account
nav — links to other pages
main — the actual content of this page
aside — related links, ads, filters
footer — contact, legal, sitemap

  
<header>

The top band

Logo, site title, search, sign-in. Usually identical on every page.

<nav>

Getting around

The main set of links. Screen readers offer to jump straight to it.

<main>

The point of the page

Exactly one per page, and it holds the content that is unique to this page.

<aside>

Beside the point

Related links, filters, adverts. Useful, but not the main story.

<footer>

The bottom band

Contact details, copyright, sitemap, social links.

<section>

A chunk with a heading

Each lesson on this page is a <section>. Give it a heading and it means something.

Why not just use <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.

03The blocks every site is made of

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.

Pick a block

Result — it's live, click it

  

Columns: the three ways

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.

Grid

Fixed number of columns

You know you want 3. grid-template-columns: repeat(3, 1fr).

Grid

As many as will fit

You don't care how many. repeat(auto-fit, minmax(220px, 1fr)).

Flex

Sidebar plus content

One fixed, one flexible. grid-template-columns: 260px 1fr.

The accordion, for free: the block above is built from <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.

04Flexbox — arranging things in a line

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.

Container

Item 2 only

Result — the dashed box is the flex container
1
2
3
4
5

  
The one-line trick: display:flex; justify-content:center; align-items:center; centres anything, horizontally and vertically. This used to be a famously hard problem.

05CSS Grid — arranging things in two directions

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.

Grid container

The pink item

Result
1
2
3
4
5
6
7
8

  
The magic value: 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.

06Responsive: one page, every screen

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.

Pretend screen width

The layout below switches at 700px — that's a media query firing. The heading size uses clamp(), so it slides smoothly instead of jumping.
Result
Fluid heading
main
side

  
%

Relative to the parent

width:50% — half of whatever contains it.

rem

Relative to root font size

The safe unit for text and spacing. Respects the user's browser font setting.

vw / dvh

Relative to the screen

100dvh = full viewport height, correctly, even with mobile browser bars.

clamp()

min, ideal, max

clamp(1rem, 4vw, 3rem) — grows with the screen but never gets silly.

07Container queries — the modern upgrade

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.

Drop the same card in…

The card's CSS is identical in both slots. It rearranges itself purely from the width it was given. Below 380px it stacks and hides the blurb.
Result
🦫

Capybara Weekly

The world's chillest rodent, delivered to your inbox every Thursday.

Subscribe

  

08Inputs & controls — the browser gives you a lot for free

Before 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.

Native controls

Result

  
Accessibility, free: because these are real <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.

09Dialogs, popovers & disclosure — no library needed

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.

Try each one

The dialog traps focus and closes on Escape — you didn't write that. The popover closes when you click anywhere outside — you didn't write that either.
Result
What is a disclosure widget?

<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.

Can I style it?

Yes — details[open] lets you restyle the open state, and ::details-content lets you animate the reveal.

I'm a popover.

Rendered in the browser's top layer, so I'm never trapped behind overflow:hidden. Click anywhere else to dismiss me.


  

A real modal dialog

Focus is trapped in here. Escape closes me. The page behind is inert. All of that is one HTML element plus .showModal().

10Forms that validate themselves

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.

Validation style

Switch to "eager" and start typing an email — it turns angry red on the first keystroke. That's why :user-valid exists.
Result
Needs an @ and a domain.
8+ characters.

  

11Colour, depth & glass

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.

Surface designer

Result

Surface

Every app UI is this, repeated.


  

12Transition & animation

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.

Timing

Result — click Play, or click the ball
go

  
Performance rule: animate 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.

13Scroll-driven effects

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():

🎯 Enter

Animation is tied to how far into view the element is — not to a timer.

⚡️ Smooth

Runs off the main thread, so it stays at 60fps while JavaScript is busy.

♿️ Respectful

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:

1
2
3
4
5
Fallback thinking: in a browser that doesn't support scroll timelines, these elements simply appear normally. That's progressive enhancement — the site works everywhere, and gets nicer where it can.

14Events — how a page listens

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.

Listen for…

Interact with the panel on the right. Click it, hover it, focus it and press a key.
Result
interact with me

  

15State drives the UI

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.

Mutate the state

Result — rendered only from the object below
0
Likes
state (live)

      

  
Why this matters: with direct DOM poking, ten features means ten places that can disagree with each other. With state + render, there is exactly one source of truth, and the screen is just a photograph of it.

16Putting it together: a real reactive app

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.

0 left

    Nothing here yet. Add something above. ↑

    Look for the pattern: every button just edits the todos array and calls render(). No button knows what the screen looks like. That separation is the entire reason front-end frameworks exist.

    172026 CSS superpowers

    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.

    :has()

    The parent selector

    Style an element based on what's inside it. Impossible for 20 years.

    nesting

    Sass, built in

    Nest rules natively. One less build tool in your project.

    clamp()

    Fluid everything

    Type and spacing that scale smoothly without a single breakpoint.

    color-mix()

    Maths on colours

    Blend two colours in CSS — hover states derived from one brand colour.

    oklch()

    Perceptual colour

    Lightness that actually looks equal across hues, plus wider-gamut screens.

    @layer

    Cascade layers

    Decide which stylesheet wins, on purpose, instead of fighting specificity.

    view-transition

    Native page transitions

    Cross-fade or morph between states and pages — app feel, zero framework.

    :is() / :where()

    Cleaner selectors

    Group selectors readably and control specificity deliberately.

    Play with :has() — the parent selector

    Tick 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; }

    View Transitions — app-like state changes

    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.

    Fluid type with clamp()

    One line replaces four breakpoints. Drag to resize the box — the heading scales continuously between a floor and a ceiling.

    clamp(min, ideal, max)

    Result
    Text that fits.
    
      

    18Cheat sheet, then go build

    Centre anything

    display:grid;
    place-items:center;

    Auto-responsive gallery

    display:grid;
    grid-template-columns:
     repeat(auto-fit,minmax(220px,1fr));

    Navbar

    display:flex;
    justify-content:space-between;
    align-items:center;

    Fluid type

    font-size:clamp(1rem,2.5vw,2rem);

    Smooth hover

    transition:transform .2s ease;
    &:hover{transform:translateY(-3px)}

    Respect the user

    @media (prefers-reduced-motion:reduce){
      *{animation:none!important}
    }

    Six habits that make you look senior

    1. Use the right element. <button> for actions, <a> for navigation. Accessibility mostly comes free if you do.
    2. Design mobile-first. Write the narrow layout, then add complexity upward with min-width queries.
    3. Use CSS custom properties for colour and spacing. One change updates the whole site.
    4. Animate transform and opacity only. Everything else risks jank.
    5. Test with the keyboard. Tab through your page. If you can't reach something, neither can a lot of your users.
    6. Check caniuse.com and Baseline before shipping a shiny new feature, and provide a plain fallback.

    Putting this online (Cloudflare Pages)

    This whole site is a single index.html with no build step, so deploying is genuinely a drag-and-drop:

    1. Put index.html in a folder on its own.
    2. Go to the Cloudflare dashboard → Workers & PagesCreatePagesUpload assets.
    3. Drag the folder in, name the project, hit deploy. You get a free *.pages.dev URL with HTTPS and a global CDN.
    4. Prefer git? Push the folder to GitHub and connect the repo instead — leave the build command empty and set the output directory to the folder. Every push then redeploys itself.
    5. CLI alternative: npx wrangler pages deploy . from inside the folder.