Something not making sense? For help or questions, ping @mikestilling in Slack.
Most of our job is placing rectangles and text on screens. This mostly boils down to a bit of HTML and CSS. By the end of this lesson, you'll know how to do what you've done in Figma, in code.
Designing webpages in code is more similar to Figma or Google Docs than you'd imagine, but on steroids. Using HTML, we'll structure our design, using CSS we'll apply visual styles to our HTML. Later on, we'll use JavaScript to do stuff, too.
HTML is more like a well structured Google Doc. In Google Docs, you may apply heading formatting appropriately so that you get a handy-dandy table of contents for free. Similarly, in HTML, using the proper "elements" like, <h1>, gives us free SEO points and helps users with accessibility needs.
At the same time, in either Google Docs or HTML, you could roll without any defined headings, etc. and just style plain ol' text to look the part (though, we then don't get those other things for free).
Similar to when you'd create a new Google Doc to capture distinct content, you'd do the same for an HTML file. Essentially, each HTML file defines a specific webpage. In the protohelm codebase we use .webc files in place of HTML files. However, the code within the WebC files is essentially the same as HTML—these files just have a bit more sauce—no need to worry about that yet.
The design panel on the right side of Figma is literally a CSS applicator, but with limited features relative to actual CSS—similarly, the top bar of Google Docs. The only difference between the tools you know and HTML/CSS is how styling is applied.
In code, we don't get a panel with preset styling fields (think, the text styling options in Figma). Rather, we have to either target HTML elements to style in our CSS code and apply visual attributes to them, or apply styles directly to our HTML with predefined bits of CSS styling.
The latter approach is referred to as Utility Classes. Utility classes have been popularized by TailwindCSS, which we'll be using. They usually look like:
<h1 class="text-32/150 font-medium">
This is a heading 1 HTML element with utility classes
</h1>In the above, the utility classes are added into the class="" part of the opening tag of the <h1> element. These specific classes would set the heading's font size to 32px, its leading (line-height) to 150%, and its weight to medium (500). Tailwind became popularized because it generates utility classes for almost all CSS properties.
We're using utility classes here because it's a less complex way to get started with HTML/CSS and typically much more efficient, process-wise.
Let's get to making... 👇
In Cursor, to create a new webpage in our protohelm codebase, we're going to start by right-clicking or ⌘-clicking on the src folder visible in the left side bar.
Upon clicking, you'll get the option to create a new file. Click that option. After clicking, a new file will be added within the src folder and you'll have the ability to start typing out its name. Let's name it first-page.webc.
At this point, you'll have created a completely blank new webpage. It should have opened up a tab in Cursor by default. Prior to trying to view the page in a browser, let's add some foundational elements and content in...
The first thing we'll do is add front matter into the first-page.webc file. Front matter isn't a standard part of HTML files—it is some of .webc's special sauce.
The content we put into front matter funnels into templated slots within an HTML file. Think of it like printing on letterhead -- it prevents us from having to specify the same boilerplate for every single page.
Front matter uses YAML code. For the most part, YAML is just key value pairs, kind of like a two column table.
To breakdown what our front matter is going to look like and do:
---
layout: (where we select the underlying template the page will use)
title: (sets the internal codebase's title/name of the page)
seoTitle: (sets the title that appears when the page is shown in Google search results and, also, the text in the browser tab when the page is open)
ogTitle: (sets the title that appears when a link to the page is shared on social media)
seoDesc: (sets the description that appears when the page is shown in Google search results)
ogDesc: (sets the description that appears when a link to the page is shared on social media)
ogImage: (a relative file path to the image that appears when a link to the page is shared on social media)
ogImageAlt: (descriptive alt text for the image above)
changefreq: (the likely cadence that this page will be updated [weekly, monthly, yearly])
---Knowing how this front matter stuff works, let's paste some into our first-page.webc file. I've written out some code that you can copy for this below. Be sure to copy/paste over the --- at the top and bottom!
---
layout: "main.webc"
title: "First page"
seoTitle: "Stripe | My first page"
ogTitle: "Stripe | My first page"
seoDesc: "This is the first new page I made in my codebase."
ogDesc: "This is the first new page I made in my codebase."
ogImage: "/assets/images/og/default.jpg"
ogImageAlt: ""
changefreq: "yearly"
---With the core templating taken care of, let's add visual elements to our new page!
Since the navigation and footer of stripe.com is the same pretty much everywhere, I've made components we can use for them. These components are using some more of the .webc special sauce and will not work with standard HTML.
In our first-page.webc file, below our front matter, let's add in stripe.com's navigation. To do this, all we need to add is:
<helm-nav></helm-nav>Below the navigation, we'll add in the stripe.com footer, too. We'll do this by adding another component that I've already created:
<helm-footer></helm-footer>Since we have those two elements in place, the last thing we need to do is start adding in some unique content for the page.
We'll add our page's content within a <main> element. Similar to most HTML elements, this requires an opening <> and closing </> tag. All in, our page's code should now look like this:
---
layout: "main.webc"
title: "First page"
seoTitle: "Stripe | My first page"
ogTitle: "Stripe | My first page"
seoDesc: "This is the first new page I made in my codebase."
ogDesc: "This is the first new page I made in my codebase."
ogImage: "/assets/images/og/default.jpg"
ogImageAlt: ""
changefreq: "yearly"
---
<helm-nav></helm-nav>
<main></main>
<helm-footer></helm-footer>To create a new section on this page, we'll start by opening up our <main> element. To do this, click between the <main> element's opening and closing tags. Hit the enter key three times to add space between them.
Now, place your cursor in the middle space between <main>'s opening and closing tags. Here we'll add a section and container component:
<main>
<helm-section>
<helm-container>
</helm-container>
</helm-section>
</main>Section and container components like this are the foundation to modular page designs on the web. To show what these are, I've overlayed a diagram on a section of stripe.com below:

The section component sets up a horizontal block across the page. Since page content on most sites doesn't bleed to the edge of the window, the container component sets the standard maximum width of content within section block.
Similar to the diagram above, to add another section to the page, you would just duplicate the code above and add new content within container component.
For this lesson, we'll add a standard, side-by-side layout to our content. On the left, we'll have our text. On the right, we'll have a visual.
We'll apply utility classes to the container component that define the layout of the content within it. Let's build a mini spec to figure out what styles we'll need to apply:
2 column grid layout16px gap between themcenter align vertically96px padding above and below the content16px padding on the sides of the contentgray grid lines on the left and right edge of the containerIn code, this is how we apply this:
<helm-section>
<helm-container class="grid grid-cols-2 gap-16 items-center py-96 px-16 border-x border-edge">
</helm-container>
</helm-section>Hopefully the above seems fairly straight forward thanks to Tailwind. The only gotcha may be the border-edge class. This class is sets the color of the border.
The attribute edge is a CSS variable that I've setup within Tailwind that matches the normal border color used in Helm.
Learning how CSS grid and flex layouts work is a game changer. While we'll only briefly touch on it in this course, there are lots of resources online to learn more. My personal favorites are CSS Tricks Grid and Flex articles.
Next, we'll add content to our 2-column grid layout. Let's scaffold this out first with some code comments and HTML. Code comments are the <!-- note --> stuff in the code below.
On the left, we'll add a <div> with our content in it. Content-wise, we'll add a headline and subhead. On the right, we'll add another <div> that has a border radius and border applied to it. We'll put an image into this later on. Here's the code:
<helm-section>
<helm-container class="grid grid-cols-2 gap-16 items-center py-96 px-16 border-x border-edge">
<!-- Column 1: Text -->
<div>
<h1>
Here's our sections headline using a headline one tag
</h1>
<p>
This is a description below the section headline. Naturally, this P tag will stack vertically below the headline.
</p>
</div>
<!-- Column 2: Visual -->
<div class="flex aspect-square border border-edge rounded-6">
<!-- Add image here later -->
</div>
</helm-container>
</helm-section>To see what this makes, let's fire up a dev server via Cursor's Terminal and visit the page. If it's not already running, type npm start into the Terminal and hit enter.
To view the page, go to localhost:8080/first-page/ in your browser.
Keep the development server running. We'll use it to check how our design updates look as we start styling the content.
This next part hopefully feels familiar if you're a designer. We'll be applying CSS properties to the text to style it, just like we would in Figma.
Let's style the headline (<h1>) element. We'll set the font size to 32px, the leading (line-height) to 110%, tighten the tracking, and add some margin below it:
<h1 class="text-32/110 tracking-[-0.02em] mb-16">
Here's our sections headline using a headline one tag
</h1>Here, text-32/110 sets font-size and line-height. The tracking class looks a little funky with the brackets around its value, eh? Adding brackets around any value in a Tailwind class lets us break outside of preset values. If a value doesn't seem to be working, throw some brackets around it. Chances are, it's just outside the preset list.
Tap ⌘S to save, then hop back to your browser to see the update. Looking better! Now let's style the description. We'll bump its font size to 18px and set the text color to Soft—which equates to Neutral 600 in Helm's color scale:
<p class="text-18 text-neutral-600">
This is a description below the section headline. Naturally, this P tag will stack vertically below the headline.
</p>Save again and check the browser. The rag on this content is also horrid. Let's fix both elements at once by adding text-balance to the parent <div>. The <h1> and <p> are "child" elements of the <div>, so they'll inherit this style:
<!-- Column 1: Text -->
<div class="text-balance">
<h1 class="text-32/110 tracking-[-0.02em] mb-16">
Here's our sections headline using a headline one tag
</h1>
<p class="text-18 text-neutral-600">
This is a description below the section headline. Naturally, this P tag will stack vertically below the headline.
</p>
</div>Another ⌘S and peek should show the content looking pretty decent. Now onto that big honkin' square next to it...
The square aspect ratio feels off, let's change it to 16/9. We'll also set the background to Neutral 25 (the light gray from stripe.com's homepage), add relative positioning for the image we'll drop in later, and hide any overflow:
<!-- Column 2: Visual -->
<div class="relative flex aspect-16/9 overflow-hidden bg-neutral-25 border border-edge rounded-6">
<!-- Add image here later -->
</div>Save the file and take a look in the browser. Looking better ✅.
Now let's add an image. The <img> tag in HTML is self-closing (no separate closing tag needed). We'll point it at an image I've saved in /src/assets/images/util/:
<!-- Column 2: Visual -->
<div class="flex aspect-16/9 bg-neutral-25 border border-edge rounded-6">
<img src="/assets/images/util/triplo.png" width="1818" height="1020" class="relative w-full h-auto" alt=""/>
</div>After you save and take a look at the page, it should now look something like this:

And your final code:
---
layout: "main.webc"
title: "First page"
seoTitle: "Stripe | My first page"
ogTitle: "Stripe | My first page"
seoDesc: "This is the first new page I made in my codebase."
ogDesc: "This is the first new page I made in my codebase."
ogImage: "/assets/images/og/default.jpg"
ogImageAlt: ""
changefreq: "yearly"
---
<helm-nav></helm-nav>
<main>
<helm-section>
<helm-container class="grid grid-cols-2 gap-16 items-center py-96 px-16 border-x border-edge">
<!-- Column 1: Text -->
<div class="text-balance">
<h1 class="text-32/110 tracking-[-0.02em] mb-16">
Here's our sections headline using a headline one tag
</h1>
<p class="text-18 text-neutral-600">
This is a description below the section headline. Naturally, this P tag will stack vertically below the headline.
</p>
</div>
<!-- Column 2: Visual -->
<div class="relative flex aspect-16/9 overflow-hidden bg-neutral-25 border border-edge rounded-6">
<img src="/assets/images/util/triplo.png" width="1818" height="1020" class="relative w-full h-auto" alt=""/>
</div>
</helm-container>
</helm-section>
</main>
<helm-footer></helm-footer>You did it! You made a section on a webpage. It may seem kind of lame, but it's a start. We're almost done covering the essentials—then we'll use AI to augment and 100x the capabilities we just learned.
We've made a lot of changes to our code, now is a good time to pop open GitHub Desktop and sync our changes to GitHub.
Next to the avatar in the bottom right of the GitHub Desktop app, let's add a commit message of Built my first page. Then click the commit button below the message/description. After committing, click the blue Push origin button over to the right side of the screen.
Give it a minute and revisit the Netlify/Vercel link we created in the previous lesson. You'll need to add the slug /first-page to the URL. Updates should be visible here.
Somehow, I keep making each of these lessons longer than the previous... Sorry. Anyhow, congrats on finishing this one!!!
Again, things may still seem a bit fuzzy—that's okay we just covered a lot and didn't go too deep. That is intentional. My hope is that you're understanding the gist of the process. Understanding enough is going to make using AI later on a lot easier. For instance, you'll now be more able to:
You've also now seen the normal feedback loop when crafting interfaces in code. Write a little bit of code → save it → preview it in the browser. After a while, this becomes second nature and it all happens instantly. I honestly believe I can design faster this way than I would otherwise in Figma.
In Lesson 4 – Make it interactive, we'll get to use AI for the first time. We're going to mess around with code-based animation and try generating a WebGL background for our image. Click on the next lesson below to start.