8/15/2026
Building a site in 2026
CSS has come a long way since my last portfolio. I used this rebuild to explore the newer properties and browser APIs. Here’s what I tried and how I found working with them.
CSS has come a long way since I built my last portfolio about six years ago. I’d been keeping an eye on the new features, but hadn’t had much opportunity to use them in my day-to-day work. After being laid off in March, I decided to rebuild the site and give myself some room to explore.
I wanted to try as many of the newer Baseline-available CSS properties and browser APIs as I could reasonably use. Container queries, relative colours, view transitions, native popovers. A portfolio gave me somewhere to put them through their paces with real content, including all the awkward layout problems I tend to leave out of a quick demo.
I’m a Vue guy at heart, so I used Nuxt 4 for the development experience and server-side rendering. For the interface, I reached for native HTML and CSS first. Here’s what I used and how I found working with it.
Plain CSS
I dropped SCSS for this project, and it was easier than I expected. Native nesting and custom properties cover most of what I was using it for. Functions like clamp(), min(), and max() also let me express a lot of sizing decisions directly in the styles.
I’ve enjoyed having the values available in the browser. I can change a custom property, see what responds, and follow it through the stylesheet. There’s still a build pipeline processing the CSS, but I have one less syntax and dependency to maintain.
Breakpoints are still a bit annoying to keep consistent. Otherwise, I haven’t missed Sass much. If you’ve been considering making the move, a personal project is a pretty good place to try it.
Container queries
These were high on my list. The site has galleries, sidebars, and case-study sections that can sit at very different widths. Container size queries let those pieces adapt to the space they’re actually in.
A desktop viewport doesn’t tell me how much room a gallery has once the navigation and supporting content are accounted for. Being able to query the gallery’s container makes that layout much easier to reason about. It also gives me more freedom to reuse a component somewhere narrower without adding another special case.
I used container style queries too, which let descendants respond to custom-property values on an ancestor. On this site, that’s useful for reacting to layout tokens. They take a little getting used to, particularly the fact that you’re styling descendants of the container you’re querying.
Of everything I explored, containers are among the things I’m most interested in carrying into future work. They make responsive behaviour easier to define at the component level, where many of those decisions belong.
Grid and subgrid
With that flexibility, I wanted a consistent structure underneath the pages. I used a compound grid with named columns for narrow text, wider figures, and full-width sections. Subgrid lets nested content line up with those same columns.
The stationery influence probably shows up most here. I’ve always liked the way planners and forms use alignment and rules to organise a lot of information. The grid gave me room to explore that while keeping a relationship between sections with very different content.
I also capped the main content width. Designing every section to keep expanding across an ultrawide monitor was more work than I wanted to take on for this site. A comfortable reading width mattered more to me.
The setup takes some forethought, but placing a new section is easier once the column names mean something. I’m making fewer arbitrary width decisions as I go.
Relative colours and light-dark()
I went fairly deep on colour. The palette uses OKLCH, with custom properties for hue and chroma, so I can change the colour of a page while keeping a related set of tones.
Relative colour syntax helps with the smaller details. The grid backgrounds, for example, take an existing colour and adjust its alpha. I can change the source colour without also finding and updating a separate translucent version.
I used light-dark() for the light and dark theme values. With the page’s color-scheme set, a colour token can contain both values. That keeps the theme decisions together and saves repeating rules throughout the components.
This has been fun to play with. It makes trying a different colour treatment cheap enough that I’ll actually do it. I still need to check how the combinations look and whether there’s enough contrast; some hues need more adjustment than others.
View transitions
These took the most effort to understand. The syntax, the snapshots, the pseudo-element tree: there’s quite a bit to get your head around. Bramus’s writing helped me get going.
I used same-document view transitions for opening gallery content into a focused view. I wanted the selected image to visibly move into its new position so it was easy to follow what had happened.
The browser does a lot of the animation work, but coordinating it with the interface still takes care. Transition names need to match and be unique, and the state change needs to happen at the right time. Getting that to work alongside a dialog added another timing problem to work through.
I’m pleased with how these interactions feel. I’d use the API again for this kind of transition, though I’d allow time for experimentation. Focus management, reduced-motion preferences, and the behaviour without animation still need attention alongside it.
Dialogs and popovers
I used native dialogs and popovers throughout the site, including the supporting notes in my case studies. I wanted to find out how much of that behaviour I could hand over to the browser.
A modal dialog handles the top layer and makes the rest of the document inert. An auto popover can close when someone clicks outside it. Those are useful things to have built in, and I’ve wrapped the elements in Vue components so I can use them consistently.
Honestly, I’m still mixed on the experience of working with them. Positioning and coordinating opening and closing have sometimes felt more involved than I expected. Anchor positioning is promising, though support for newer features needs checking separately.
I’ll keep using them. I just wouldn’t assume that choosing a native element makes the whole interaction straightforward to build.
System typography
I explored a more distinctive display font early on, then moved to system fonts. I liked the idea of the site feeling familiar on the reader’s OS, and it gave me an interesting design constraint to work with.
system-ui uses the device’s interface font. I also use native serif and monospace options in the wider stack. That means the typography changes across platforms, so I’ve been tuning heading profiles for the different proportions of San Francisco, Segoe, and Roboto.
Some modern system fonts support variable weights and optical sizing. Segoe UI Variable is one example. The controls available depend on the installed font, OS, and browser, so I can’t assume a particular weight or axis will render the same way everywhere.
There’s also no web-font download needed for the main interface. The intended type is already on the device, which removes that loading step and the possibility of a late web-font swap.
I’ve enjoyed seeing how much personality I can get from familiar type. Weight, scale, spacing, line length, and alignment all become more noticeable when the font itself is understated. Combined with the colour and borders, I think there’s enough there to make the site feel like mine.
A few other useful bits
There are smaller features scattered throughout the styles too. :has() is useful for responding to a component’s contents; I’ve used it with :user-invalid for form feedback. Small and dynamic viewport units give me more control over layouts affected by mobile browser chrome. round() has been handy for a few sizing and spacing calculations.
And I still get a surprising amount of satisfaction from border-style: double. Paired with rounded corners and a solid line opposite it, it does a lot for the stationery feel of the site.
What I’d take into the next project
I’d happily start another project with plain CSS, container queries, and this approach to colour. View transitions are something I’d make time to explore when an interaction would benefit from that visual continuity. I’m still finding my feet with native dialogs and popovers.
Baseline has been a useful reference throughout, though the features I’ve mentioned are at different stages of support. I need to check the specific capability I’m using, especially when it affects whether someone can access content.
I also used AI during the build, after setting up the tokens, typography, and layout. It helped with boilerplate, data structures, and small tools for creating interactions. With newer browser features, I had to pay particular attention to whether the output matched the current API and the behaviour I wanted. It was much easier to assess once I understood the feature myself.
There’s plenty here I’m still adjusting. But I’ve come away with a much better feel for what the browser can do now, and a few ideas I’d like to try in product work. If you’ve been experimenting with any of these, I’d love to compare notes.