What is a component anyway?

I previously said web components are not components, so it’s only fair that I expand on my idea of a component.

At the most basic level, a CSS class can be considered a component. It’s an intuitive way to reuse common styles in multiple places. Bootstrap is a very popular demonstration of this idea working at scale.

More commonly, components provide us a way to structure our markup, starting from the smallest pieces (an <Icon> component, used within a <Button>) and potentially building all the way up to the entire <Page>. At each level, the component “hides” the underlying markup, whether that’s DOM elements or attributes. Crucially, the components must be “composable” for this to work; smaller components must be able to fit into bigger ones. You’ve probably heard the Lego blocks analogy used to describe this idea.

Notice how we haven’t even talked about JavaScript so far. When a user visits our website, the very first thing they experience is the markup (and usually styles). You would think then, that components built into the platform should start with HTML. But I digress.

After the markup and styles, we may indeed want to sprinkle some client-side JavaScript. Ideally, this is designed as an optional enhancement rather than a critical dependency. Components should provide a way to seamlessly author this JavaScript in the same place as the markup.

Modern frameworks like Svelte and Vue perfectly fit the definition so far. With their “single file components”, we can neatly define all related HTML/CSS/JS in one place.

For a concrete example, let’s use Svelte to create a Banner component that accepts text in a slot and contains a close button which will hide the banner.

<aside role='note' {hidden}>
<button on:click={handleClose}>Close</button>
aside { display: flex; gap: 4px }
p { margin: 0 }
/* ... other styles truncated */
let hidden = false;
function handleClose() {
hidden = true;

The CSS selectors are automatically scoped, and the JavaScript variables are automatically reactive and can be used as attributes. The authoring experience feels like fancy HTML.

And the usage feels familiar too. Assuming the file is imported as Banner, we can use it like a regular HTML tag and populate the slot:

<Banner>This feature is still a work-in-progress.</Banner>

In the end, it will look something like this (the demo might not show up in your RSS reader, sorry).

If JavaScript is present, the close button will do its thing. If not, that’s still okay, because the markup and styles are generated on the server before being sent to the user… which nicely segues to my next point.

Many frameworks have started offering “server components” that allow writing backend and frontend code in the same file. This lets us do things like reading from our database/filesystem and populating the markup immediately afterwards. This idea itself might not seem new (PHP has been doing this since the beginning), but the implementation certainly is. Examples include React Server Components, Fresh, and Astro.

Let’s look at Astro. Since it supports Svelte, we can take our Banner component from above and put it in an Astro component. We can decide what string to show inside the banner based on some backend API call. And we can also add any additional markup and scoped styles.

import Banner from './Banner.svelte';
const text = await fetch(/* some url */);
div { display: grid; place-items: center; }

Now that’s a component!