The Coyier CSS starter
I’m somewhat a fan of resets and sensible global CSS styles, so I thought it would be fun to run through Chris’ recent starter and pick out bits I like and don’t like.
One thing to preface here for everyone — and I think this is really important; everyone has their own preferences and ways to do stuff that works for them and we should celebrate that.
Chris’, as he describes himself, is not a reset, but a starter. That aligns quite nicely with the global styles first approach I teach people. Those who’ve taken Complete CSS will really recognise the value in doing as much up high as possible so already I’m really on board with what Chris is doing here.
Something I’m always keen to watch out for, with that in mind, is stuff getting in the way of the highly customised styles we apply in our day-to-day work, but on the flip side I’ve been keenly looking for stuff which is set and forget. That’s the most value I personally get from my “modern reset”, anyway.
Let’s get stuck in.
- Code language
- css
@layer reset { }
I’ll be honest, I wouldn’t use @layer
here. I don’t think it’s wrong, but it’s more I (and we at the Studio/Piccalilli) haven’t really found a legitimate need for @layer
in our work.
As Chris says, this starter isn’t to be used verbatim, but if you’re planning using it like that, make sure you understand how @layer
works to avoid future foot-guns.
This is the sort of thing I was referring to at the start of this run-through with “stuff getting in the way”. For example, the “modern reset” continues to evolve internally because elements get in the way in various different types of projects for us. We’ve actually opted to use :where()
in places to knock down some specificity, especially where we’re using attribute selectors in combination with element selectors. But that works for us and @layer
works for Chris. That’s fine.
So yeh, use @layer
with caution and knowledge (in the whole team) would be my advice.
- Code language
- css
html { font: clamp(1rem, 1rem + 0.5dvw, 2rem) / 1.4 system-ui, sans-serif; }
I’m all in with the clamped root font size. In fact, by setting this and have a global fluid typography system right off the bat. Nice.
One thing I don’t like here though is the font
shorthand setting size, line height and font family. I personally prefer separate CSS properties for this sort of thing, for readability and understandability purposes.
- Code language
- css
body { margin: 0; padding: 2rem; @media (width < 500px) { padding: 1rem; } }
I’ll be honest, I’m not feeling this one. I put global padding on the body
for demos etc. but I don’t think I’d be doing that for production work. There’s just too much risk of again, this stuff getting in the way, especially if you have full bleed elements.
- Code language
- css
html { hanging-punctuation: first allow-end last; }
And…
- Code language
- css
html { word-break: break-word; } pre { white-space: pre-wrap; }
I love, love, love these and will be stealing them.
- Code language
- css
h1, h2 { font-weight: 900; letter-spacing: -0.02rem; } h1, h2, h3 { line-height: 1.1; } h1, h2, h3, h4, h5, h6 /* FUTURE :heading */ { text-wrap: balance; margin-block-start: 0; }
Be careful setting font weight and letter spacing globally as both will be very font family specific. For example, Inter will be treated very differently to Georgia in those terms.
A tighter line height, zeroed top margin and balanced text wrapping are very useful though. Just be careful setting line height too tight if your font has large ascenders and descenders though or you’ll get overlaps and readability problems.
- Code language
- css
ul, ol, dl { margin: 0; padding: 0; list-style: inside; ul, ol, dl { padding-inline-start: 2ch; } }
I quite like the use of the ch
unit here. One thing to watch out for is ul
and ol
are not permitted direct children for dl
, but you might have them as child elements of a dd
element, for example.
Just know your lists are going to be indented by this style in that scenario. The nesting probably doesn’t surface that for you, when scanning the code.
I won’t touch on the nesting in this starter, other than what I’ve written about before.
- Code language
- css
input, select, textarea, button { font: inherit; /* FUTURE: apperance: base; */ } label { display: block; } input:not( :where( [type="submit"], [type="checkbox"], [type="radio"], [type="button"], [type="reset"] ) ) { inline-size: 100%; } button, input:where( [type="submit"], [type="reset"], [type="button"] ) { background: CanvasText; color: Canvas; border: 1px solid transparent; } textarea { field-sizing: content; min-block-size: 5lh; inline-size: 100%; max-inline-size: 100%; }
Some very useful styles in here! I agree with Chris on filling the inline space with form fields too. If you want to do a split layout for forms, the parent layout should be doing that work for you, rather than the fields themself. Essentially, the C in CUBE CSS applies here.
One tweak I’d make though is a more generous min height (min-block-size
) on the textarea
. I think 5lh
is a little stingy, but field-sizing: content
will allow the textarea
to grow with the input, so maybe not. See how it feels in your context would be my advice.
- Code language
- css
table { caption-side: bottom; border-collapse: collapse; td { font-size: 90%; } td, th { word-break: normal; border: 1px solid gray; padding: 0.5rem; } } [role="region"][aria-labelledby][tabindex] { overflow: auto; } caption { font-size: 90%; }
Often forgotten tables get some nice defaults here. I’m not a fan of percentage font sizes though, purely on a consistency basis.
The root element (html
) is nice and clamped so rem
units would be hella handy here, but I’d probably opt for em
units to achieve what Chris is gunning for here: relative sizing based on the table’s immediate context to get that nice visual balance.
I’m just picking him up on an inconsistency of the application of units here, as he’s using rem
/em
everywhere else. Makes sense to do the same for tables as I see it. Yeh I’m boring at parties.
- Code language
- css
@media (prefers-reduced-motion: no-preference) { @view-transition { navigation: auto; } html { interpolate-size: allow-keywords; &:focus-within { scroll-behavior: smooth; } } }
View transitions on every page by default? Not for me Chris, sorry. I am a grumpy old man with that sort of thing 😅
I like the smooth scroll behaviour rule though. It pairs well with Chris’ usage of scroll-margin
in this starter.
Wrapping uppermalink
Chris says this is opinionated and it’s exactly that. There’s a lot to really like about this starter, that’s for sure. Just like with every starter and reset: pick what you like and build one that works for you and your team.
These things are really handy and if you turn out a lot of different projects. They save a tonne of time in the long term too, so it’s really worth investing the time in making one. You’ll learn a lot about how user agent styles work while you’re at it too.
Check it out Advert