Styleguide

This page serves as an HTML and CSS styleguide for the content and components found on on this site. The initial page structure came from FinSweet's Client-First. It is a framework that helps you create Webflow sites with proper HTML structure and a CSS naming structure tailored for Webflow.

Custom Components

Native Webflow elements with Client-First classes applied.

Inline Code
This is some text inside of a div block.
Tags
Subscribe Card
Subscribe

Thanks for reading! Subscribe to get an email whenever I post or subscribe via RSS feed.

Social Card

I’m Jose Munoz, a product designer from Puerto Rico working at BOLD. I enjoy spending time with my wife and our dog max and write about tech, automation, lego. Learn more.

Say hi via Mastodon, Threads, email.

Prev/Next Post
Blog Series
More in this series:
Text LinkText LinkText Link
Notion Inline Callout/Block
😅

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

😅

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

😅

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

😅

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

Media Cards

For use in posts and collections

Death Star Trash Compactor Diorama
A cheap little case for my MX Master 3 so I can toss my mouse in my suitcase or backpack when traveling.

Media Card Horizontal - Master Component

Lego Media Cards Master

App /Shortcut Media Card Master

Product Media Card Master

CMS Master: App Shortcut

CMS Master: Product Card

CMS Master: Lego

Old Card Style

Inquisitor Transport Scythe

Structure Classes

Defined and flexible core structure we can use on all or most pages.

page-wrapper
main-wrapper
container-small
container-default
container-large
padding-global
padding-section-small
padding-section-default
padding-section-default-top
padding-section-large
padding-section-large-top
button-group

Structure Classes

Defined and flexible core structure we can use on all or most pages.

HTML Heading Tags

HTML tags define default Heading styles.

H1

Sample text helps you understand how real text may look. Sample text is being used as a placeholder.

H2

Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text that is normally present.

H3

Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text that is normally present.

H4

Sample text is being used as a placeholder. Sample text helps you understand how real text may look. Sample text is being used as a placeholder for real text that is normally present.

H5
Sample text is being used as a placeholder. Sample text helps you understand how real text may look. Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look.
H6
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look. Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look.

Other HTML Tags

HTML tags define default text styles.

All paragraphs

Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text.

All links
All Links
All quotes
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website.
All Ordered Lists
  1. Sample text is being used as a placeholder for real text that is normally present.
  2. Sample text is being used as a placeholder for real text that is normally present.
  3. Sample text is being used as a placeholder for real text that is normally present.
All Unordered Lists
  • Sample text is being used as a placeholder for real text that is normally present.
  • Sample text is being used as a placeholder for real text that is normally present.
  • Sample text is being used as a placeholder for real text that is normally present.

Heading Styles

Heading classes when typography style doesn't match the default HTML tag.

heading-style-h1

Sample text helps you understand how real text may look.

heading-style-h2

Sample text is being used as a placeholder. Sample text helps you understand how real text may look.

heading-style-h3

Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text that is normally present.

heading-style-h4

Sample text is being used as a placeholder. Sample text helps you understand how real text may look. Sample text is being used as a placeholder for real text that is normally present.

heading-style-h5

Sample text is being used as a placeholder. Sample text helps you understand how real text may look. Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look.

heading-style-h6
Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look. Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look.

Text Classes

Text classes when typography style doesn't match the default HTML tag.

Text Sizes

text-size-large

Sample text is being used as a placeholder for real text that is normally present.

text-size-medium

Sample text is being used as a placeholder for real text that is normally present on your website.

text-size-default

Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website.

text-size-default-prose

Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website.

text-size-small

Sample text is being used as a placeholder for real text that is normally present on your website. Sample text helps you understand how real text may look on your website.

text-size-tiny

Sample text is being used as a placeholder for real text that is normally present on your website. Sample text helps you understand how real text may look on your website.

Text Styles

text-style-strikethrough

text-style-strikethrough

text-style-italic

text-style-italic

text-style-muted

text-style-muted

text-style-allcaps

text-style-allcaps

text-style-nowrap

text-style-nowrap

text-style-link
text-style-quote

Sample text is being used as a placeholder.

text-style-2lines

Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text text-style-2lines

text-style-3lines

Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text text-style-2lines

text-style-nodecoration

Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text.

Text Weights

text-weight-semibold
text-weight-semibold
text-weight-medium
text-weight-medium
text-weight-regular
text-weight-regular

Text Alignments

text-align-left
text-align-left
text-align-center
text-align-center
text-align-right
text-align-right

Buttons

Button combo class system.

button
Button Text
button
is-small
Button Text
button
is-large
Button Text
button
is-secondary
View collection in Notion
button
is-secondary
Button Text
button
is-text
Button Text

Colors

Manage recurring text and background colors.

Color Palette

#00000
#f5f5f5
#fffff

Text Colors

text-color-primary
text-color-primary
text-color-secondary-subtle
text-color-secondary-subtle
text-color-subtler
text-color-tertirary-subtler
text-color-tertirary-bullets
text-color-tertirary-subtler
text-color-blue-primary
text-color-blue-primary
text-color-onprimary
text-color-onprimary

Background Colors

background-color-black
background-color-default
background-color-white

Max widths

Use the max-width CSS property to contain inner content to a maximum width.

max-width-full
max-width-full-tablet
max-width-full-mobile-portrait
max-width-full-mobile-landscape
max-width-xxlarge
max-width-xlarge
max-width-large
max-width-medium
max-width-small
max-width-xsmall
max-width-xxsmall

Border Radius

Border Radius

radius-default
radius-large
padding-vertical

Paddings

Utility spacing system - padding classes. [padding-direction] + [padding-size].

Direction Classes

padding-bottom
padding-top
padding-vertical
padding-horizontal
padding-left
padding-right

Size Classes

padding-0
padding-tiny-2px
padding-xxsmall-8px
padding-xsmall-12px
padding-small-16px
padding-medium-20px
padding-large-24px
padding-xlarge-32px
padding-xxlarge-40px
padding-huge-80px
padding-xhuge
padding-xxhuge
padding-custom1
padding-custom2
padding-xxxsmall-4px
padding-xxxlarge-56px

Margins

Utility spacing system - padding classes. [margin-direction] + [margin-size].

Direction Classes

margin-bottom
margin-top
margin-vertical
margin-horizontal
margin-left
margin-right

Size Classes

margin-0
margin-tiny
margin-xxsmall
margin-xsmall
margin-small
margin-medium
margin-large
margin-xlarge
margin-xxlarge
margin-huge
margin-xhuge
margin-xxhuge
margin-custom1
margin-custom2
margin-custom3

Icons

Unify icons sizes. icon-height sets height of icons. icon-1x1 sets both height and width of icons.

icon-height-small
icon-height-medium
icon-height-large
icon-1x1-x-small
icon-1x1-small
icon-1x1-medium
icon-1x1-large

Useful utility systems

Utility classes we like to use in most of our projects to build faster.

hide
This element is hidden
hide
This element is hidden
hide-tablet
hide-mobile-portrait
hide-mobile-landscape
overflow-visible
overflow-hidden
overflow-auto
overflow-scroll
pointer-events-on
pointer-events-off
layer
div-square
spacing-clean
align-center
z-index-1
z-index-2
display-inlineflex
display-inlineflex-vertical
display-inlineflex-vertical-center

Webflow elements

Native Webflow elements with Client-First classes applied.

form_component

Example of a form component using Folders

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
text-rich-text

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
Heading 6

Sample text with a link is being used as a placeholder for real text that is normally present. Sample text helps you understand how real text may look on your website. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text. Sample text is being used as a placeholder for real text.

Ideas and goals

Most of the time, I'm just hacking on my site for fun and not-profit. But while building this latest version, I did manage to have a few key ideas and goals in mind:

Reduce friction for iteration and writing

Last year I wrote about why you're not updating your personal website. One of the key reasons I mentioned is that it's too hard to add and edit content for most people who roll their own codebase. If you have to open a pull request every time you want to write a note or make a small edit, you've lost.

So this new version of my website was built with the idea that I should be able to add, edit, and delete content directly from the front-end. This means that everything needs to be backed by a database or CMS, which quickly adds complexity. But at the end of the day, adding a bookmark should be a matter of pasting a URL and clicking save. Writing a blog post should be a matter of typing some markdown and clicking publish.

Extra friction on these processes would make me less likely to keep things up to date or share new things.


A playground for ideas

I've been collecting a list of frameworks and tools to try someday on Github, ranging from front-end JavaScript frameworks to small utilities for writing better CSS. I want my website to be a playground where it's safe to try new technologies and packages in a way that can be easily isolated and easily deleted. This requirement made using Next.js an easy decision because of way it supports hybrid page rendering strategies — static, server-rendered, or client-rendered. More on this below.

Fast

As noted by The Zen of GitHub, "it's not fully shipped until it's fast." While my site isn't the fastest website out there, it's still pretty fast. It turns out it's really hard to make a fast website in 2021, and between caching, preloading, and async computation, I think I've managed to make things feel snappy enough.

This is particularly important because of the layout I chose: a list-detail multi-column layout affords clicking between lots of different links in a single session, and if those links have just the slightest delay, the whole site can feel sluggish.

Social

One of my goals for this iteration was to make my website feel more social. Specifically, I thought it'd be fun to roll my own comment system so that people could share ideas, leave feedback, or point out any mistakes in my work. In the past, I had an email form at the bottom of each blog post that would send a private email, but making this social and public is more fun and adds real value to future readers.

Ideas and goals

Social

One of my goals for this iteration was to make my website feel more social. Specifically, I thought it'd be fun to roll my own comment system so that people could share ideas, leave feedback, or point out any mistakes in my work. In the past, I had an email form at the bottom of each blog post that would send a private email, but making this social and public is more fun and adds real value to future readers.

I wanted this version of my site to feel like a web application, mostly because it sounded fun! What would it look like to have a "personal application" that houses all of the things I've made? How would navigation work when I have lots of loosely related projects and content? How could I stretch beyond the single-column personal site design in a way that adds utility and creates creative design and programming problems to learn from?

  • Automerge — tells Dependabot updates to automatically merge package updates as long as all of my tests are passing.
  • CodeQL Analysis — uses GitHub CodeQL to detect vulnerabilities in my code.
  • GraphCDN — keeps my schema in sync with GraphCDN.
  • Hacker News Daily Digest — a cron job that triggers one of my API routes to send a daily digest of the top HN posts.
  • Compress images — looks for any images committed to my source files, then compresses and commits the smaller files.
  • End-to-end tests — this is the big Action that triggers tests on all new PRs, ensuring that Vercel is able to deploy my code and that I haven't broken core pages on the site.
  • Dependabot — keeps my packages up to date, ensuring that bug fixes in my dependencies are patched and merged automatically.

The result is obviously inspired by macOS and iPadOS, with a global sidebar navigation and a fully responsive multi-column layout. This design makes it easy to jump from anywhere to anywhere, and I love the way the the list-detail layout makes it easier to quickly browse through lots of related content without needing constant back-and-forward navigation.

However, this layout is a pain in the ass to build, for a few reasons:

  1. Multi-column layouts need to persist many scroll positions as users move between pages.
  2. Each column needs to maintain its own scroll area, which means having to override the body scrolling behavior. This makes mobile views a huge pain, especially on iOS where you can't rely on having a fixed viewport height. It also breaks helpful shortcuts like tapping on the status bar (on iOS) to quickly jump back to the top of the page. TODO: Figure this out.
  3. My content is a mix of static (e.g. it lives alongside the code, like the Security Checklist page or the App Dissections), third-party (e.g. Hacker News, and first-party (e.g. anything I store in my own database, like my Bookmarks or Stack). Each of these views fetches data in different ways, wants a slightly different layout, and should present a different set of controls. This meant having to think long and hard about component abstractions for things like title bars and action bars.

Next.js

Next.js is my front-end React framework of choice. I particularly enjoy using it because of the file-system based router — it's an intuitive and powerful abstraction for constructing route hierarchy. Next.js also has a huge community of people who have really put the framework through the grinder, sanding down the edge cases and coming up with creative solutions to common problems with React, data fetching, and performance. Whenever I'm stuck, I head to the Next.js Discussions on GitHub and start searching — almost every time there are others before me who have found creative solutions to hard problems.

Next.js is also fast. They do so much optimization for me, either by making my local builds faster, automatically compressing static assets, or making my deployment times blazing fast. The project's regular cadence of updates means my site gets faster over time — for free!

Persisting layouts

One of the absolute hardest problems to solve with this new design was figuring out how to persist each column's scroll position while navigating between pages. For example, if you're viewing the bookmarks list view, and you scroll down, then click an individual bookmark, the list of bookmarks should stay in the same position despite loading an entirely new route and changing the URL.

Another challenge was having the multi-column layout behave correctly on mobile. For example, if someone is viewing the site on a phone, the /bookmarks route should only show a list of bookmarks, and /bookmarks/[id] should only show the bookmark detail. However, on a larger viewport, /bookmarks should show the list of bookmarks with an empty third column placeholder, and /bookmarks/[id] should show both the list of bookmarks and the bookmark detail.

Okay, so how to solve these two problems...

For the scroll persistence, I wish I could take more credit here for figuring out some beautiful abstraction, but I actually picked up most of the hacks from Adam Wathan's blog post, Persistent Layout Patterns in Next.js.

Here's roughly how it works:

  1. Every page has a static method called getLayout that does some hackery to wrap itself in some global service providers (like the Apollo cache, or Fathom's page view tracking). Let's look at the getLayout method for rendering a bookmark detail:

Then my global _app.tsx file either executes that getLayout method, or falls back to wrapping the page with providers and the site layout:

Okay, the really weird thing about the code above that broke my brain was how we pass the entire page as a component down to the detail view with getLayout(<Component {...pageProps} />).

This means that on the /bookmarks/[id] page, the primary export is just this:

Note that this page doesn't render any of the providers, or care about where the component fits into the global multi-column layout — all of that context has been lifted up to the App component. I only care about the detail view itself, the third column, and so that's the only thing this page has to worry about rendering.

dsfdsfsdfsdfdsfsdfsdfdsfdsfdf

Frankly, I don't know exactly how to describe how all of this works without writing a full-on tutorial breaking down the code line by line (which I should probably do anyways, in a separate post!), but I show this code just to point out how weird you have to get with some of the page layout abstractions to "trick" Next.js into persisting a global layout with variable scroll positions across page changes.

Hey Hey Hey

As far as the solution for responsive columns: plain ol' CSS! It's all here and here if you want to poke around.

Styling

Tailwind

Tailwind is my favorite CSS-authoring tool...ever? It's really, really good. I wrote about my first impressions last year, and the library has held strong in real-world use. I regularly see threads on Twitter and HN with people arguing about why Tailwind is the best thing ever, or the worst thing ever, and I don't want to wade into that flame war here. I'll say this:

Tailwind is a toolkit that makes everything pretty damn good by default. The magic is in the token system, and the sensible defaults that the team built into the framework. Once I got the hang of Tailwind's semantics, I was really able to start styling my markup at the speed of thought.

Tailwind mostly comes with everything I could need out of the box, but I've slowly added a tiny bit of custom CSS to make things feel a bit more unique. TODO: Inline more of these custom styles using Tailwind's just-in-time mode.

Feel free to poke around here to see what's custom.

Markdown

I've given up on rich text editors on the web. They're so damn complicated, and every tool has their own bespoke abstractions for blocks and elements. We spent hours upon hours trying to solve weird edge cases with rich text while building Spectrum, and I never want to deal with that again.

MDX was a logical tool to reach for next — it cleverly allows for a Markdown-flavored authoring experience, but with the flexibility to embed interactive code, like React components. People like Josh Comeau have made great use of this to build interactive blog posts and engaging tutorials. But again, it just requires so much boilerplate to stand up, and once you start putting React into your Markdown, you're locked in until the next big refactor.

So in the spirit of trying to avoid having to do another huge rewrite in the next couple of years, I looked for the simplest, dumbest solution possible to publishing content on my site: plain text.

The post you're reading now is plain text stored in MySQL, rendered with react-markdown. You can see my custom element renderers here. I can enhance my Markdown using plugins like remark-gfm to add support for tables, strikethroughs, footnotes, and more.

Tradeoffs abound!

Headless UI

The last thing to call out for the front-end is Headless UI, a set of utilities created by the Tailwind crew to provide accessible components like dialogs, menus, switches, and popovers. I'm using it for my dialog component, and found it has the best balance of good-enough defaults with just the right API surface area for customization.

A great alternative to Headless UI is Radix UI.