Design Systems Hot Takes

Late start

6 min read

I’ve been in the process of writing a book over the past year and the hardest part for me isn’t the writing, it’s the publishing. I’ve changed publishing systems a few times over the course of this work. I’ll most likely have a larger post speaking about the process later, but the current way that I’m expecting to prepare the book for publishing is with CSS. Yep, that’s right. Get your @page styles ready!

Getting the options that a more appropriate layout system like InDesign would have isn’t really possible in most browsers today. If I really wanted to lean into all of the features that CSS could have, I’d consider using PrinceXML. Though as it stands right now, I think I’ll have a good book without it.

That said, there was some really challenging constraints that I had to work through. One of those things was page numbers.

Meeting expectations

One of the cool things about CSS is the counter() function. This acts a bit like using state in CSS in limited way. To explain, here’s what using state in React might look like:

function MyCounter() {
    const [count, setCount] = useState(0);
    return (
        <button onClick={ setCount(count + 1) }>
            { count }
        </button>
    )
}

We can effectively do the same thing in CSS. Here’s a basic setup with some comments that explain what each of these relate to:

ul {
    /* const [count, setCount] = useState(0) */
    counter-reset: count;
}

li {
    /* setCount(count + 1) */
    counter-increment: count;
}

li::marker {
    /* (print) count */
    content: counter(count);
}

One of the bigger differences is that the count increases when an element exists at a selector. I imagine we could get some interesting state machine stuff with selecting values from inputs. But that’s not what we’re after today. Instead, I want to delay the start of the numbers.

If you open up a technical book, you’ll find that the page numbers don’t start right after your crack open the cover. There’s a few a pages that either don’t have numbers, or have a different counting system like lowercase Roman numerals. Doing this in React would be fairly easy, we can set the start of the counter to a negative number and only print the number if the value is larger than 0:

function MyCounter() {
    const [count, setCount] = useState(0);
    return (
        <button onClick={ setCount(count + 1) }>
            { count > 0 ? count : '' }
        </button>
    )
}

In CSS, we don’t have this fine-grained conditional logic just yet. There’s definitely ways to do some tricky operations and maybe there is a way to do it using some of the newer functions, but I found something that’s a little more appropriate when dealing with counters. The new @counter-style at-rule.

Custom counters

CSS has had keywords that describe the method of counting for a long time. It’s the same way we’d often remove the style from <ul/> elements to drop the bullets for other presentations.

ul {
    list-style-type: none;
}

For something like a <ol/> element, the list-style-type is set as decimal. You may also know that <ol/> elements have a special start property that allows us to start the count at a different number. Immediately, you’d think that we’d use that like our negative setState(-3) from before. And while you can, it won’t work for my use case. Since I’m using @page to display page numbers in print media, there’s no “element” for us to apply it to. That’s ok, CSS counters have a syntax that allow us to update the start for a counter and we can make it increment at each page.

Here, I’m going to start the count at the first page, name the counter page, and increment at every page:

@page :first {
    counter-reset: page -3;
}

@page {
    counter-increment: page;
}

That’s a good start, but the problem is that the first page will print the number -3 at the bottom which is not what we’re looking for. Instead, I’d like these to be blank. To do this we’re going to need two @counter-style declarations:

@counter-style false-start {
    system: extends decimal;
    range: 1 infinite;
    fallback: blank-only;
}

@counter-style blank-only {
    system: cyclic;
    symbols:  ;
}

The first @counter-style has the name false-start which extends the decimal system which would normally be no different than the default counter. However, the first key here is that the range is set to 1 infinite which say to only use positive numbers starting at 1. When using range you can also use a fallback which says if the counter produces a number that cannot be written, use this style as a fallback.

The second @counter-style uses system: cyclic; which is normally used to cycle through the given symbols. In my case, I’ve provided whitespace. This means that when a negative number is passed into the counter, it will print whitespace. This allows me to set an offset and only print numbers starting at 1 exactly where I want them to start. Here’s the whole code using @page, and a Codepen demonstrating it using <li/> elements.

@counter-style false-start {
    system: extends decimal;
    range: 1 infinite;
    fallback: blank-only;
}

@counter-style blank-only {
    system: cyclic;
    symbols:  ;
}

@page {
    counter-increment: page;
}

@page :first {
    counter-reset: page -3;
}

@page :left {
    @bottom-left-corner {
        content: counter(page, false-start);
    }
}

@page :left {
    @bottom-right-corner {
        content: counter(page, false-start);
    }
}

Sure, my use-case is very specific but maybe there’s someone else out there looking to write a book and not looking to learn a new language to do it.

CSS is awesome and it could be awesomer. Rachel Andrew has an article published over 10 years ago that outlines some of the other cool things that we could have.

h1 { 
  string-set: doctitle content(); 
}

What the hell is this? It’s taking the content of the <title> and writing it into the <h1> on the page! While I have no idea what this means for an accessible web page, this is meant for print and adding the content into the margins of the book. This ability of grabbing HTML content to use in CSS is incredible. It would open up a lot of possibilities (and poor behavior). There’s even a full @footnote feature in there too.

Unfortunately, these aren’t in any browsers today. I guess who is printing things anyway now that the reading is done with AI? Silly me.

My Top 4