The site uses cookies that you may not want. Continued use means acceptance. For more information see our privacy policy.

TT-RSS Grid CSS

A DIY style improvement for the feedreader I use.

I’m a big believer in RSS feeds. My vehicle for consuming them is TT-RSS on Debian. And while I liked the UI well enough, on some days I’d see the flood of feed items in the list and found myself tending toward reading aversion, where I didn’t want to look at each item and would mark a lot as seen even though I hadn’t actually paid much attention.

That’s good and bad. If I don’t have the bandwidth for it today, I doubt I’ll find the extra bandwidth to go back through older items tomorrow, so wiping the slate is for the best. On the other hand, over longer terms if I’m not consuming feeds, it’s better to stop receiving them (or filter them more heavily (trim the fat)). That’s a calculus and decision of its own, for another day.

But I also thought the interface might be part of the issue. A big list with each item squished between other items tends to be harder for me to scan for keywords, to pick out the parts I want. So I decided to try the new-fangled grid-related styles to make the interface look a little different, and I could see if it made a difference in my ability to consume my RSS feeds.

As you can see from the comparison image, this does trade off compactness for the layout, but I find it easier to scroll and have less visible at once. It’s probably slightly worse in terms of picking what to read next from starred items, because you can’t see them all at once, but if you’re planning to read them all anyway, perhaps the order isn’t as important.

Because I do all of my modifications in Stylus, it is easy for me to swap between my grid style and my older style. Anyway, here’s the code for the grid part. Note that you may need to change some settings for TT-RSS to use this, particularly the “Group by feed” option. (It may work without it, and certainly a modified version could, but I didn’t test any variety of configurations, only what I use.)

Ahem, the code:

/* The main headline area.*/
#headlines-frame {
    display: grid !important;
    /* Use 5 columns; could be tweaked depending on your screen. */
    grid-template-columns: repeat(5, 1fr) !important;
    grid-auto-rows: min-content !important;
    column-gap: 0px !important;
    align-items: start !important;
    justify-content: space-around !important;
    row-gap: 2rem !important;
    margin-block-start: 1rem !important;
}

/* If you include .feed-title in this selector, it will make the feed group act as a horizontal divider.
    You should also enable it for the next one, which makes sure it doesn't take up too much space. */
/*.feed-title,*/
.whiteBox,
#headlines-spacer {
    grid-column: 1 / -1 !important;
}

/*.feed-title,*/
#headlines-spacer {
    max-height: 2em !important;
}

div.feed-title {
    border-width: 1px 1px 1px 1px !important;
    margin-inline: 1rem !important;
}

/* You'd disable these if you enable .feed-title above. */
.feed-title {
    /* You could span more than one, in which case it will stick out in the grid.
        Disabling the grid-column rule would treat the .feed-title as a .hl (headline),
        placing it in the next available grid slot. */
    grid-column: 1 / span 1 !important;
    padding-inline-end: 1.4rem !important;
    display: grid !important;
    grid-template-columns: 1fr auto !important;
    grid-template-rows: auto 1fr !important;
    height: 100% !important;
}

/* Positions the feed's icon in the center of the grid cell. */
.feed-title div:first-child {
    display: grid !important;
    grid-area: 2 / 1 / -1 / -1 !important;
    align-self: center !important;
    justify-self: center !important;
    justify-content: space-around !important;
    align-content: space-around !important;
    vertical-align: middle !important;
    height: 150px !important;
}

.feed-title .icon {
    vertical-align: middle !important;
}

/* Place the title in the upper-left of the cell. */
.feed-title .title {
    grid-area: 1 / 1 / 1 / 2 !important;
}

/* Place the catchup button in the top-right of the cell. */
.feed-title .catchup {
    grid-area: 1 / 2 / 1 / -1 !important;
}

/* The headlines rule.
    It defines its own grid to position the contents.
    The default (light.css and night.css) themes only give it a bottom border.
*/
div.hl {
    grid-column: auto !important;
    display: grid !important;
    height: 100% !important;
    width: 90% !important;
    grid-template-columns: 1fr 1fr 2rem !important;
    grid-template-rows: 1fr auto !important;
    column-gap: 1em !important;
    place-items: start start !important;
    margin: 0 0.25em !important;
    padding: 0.25em !important;
    border-radius: 6px !important;
    border-width: 1px 1px 1px 1px !important;
}

/* Positioning for the item's headline, author, and tags, which are members of this element. */
div.hl > div.title {
    grid-column: 1 / -2 !important;
    /* Setting the height to 100% helps to maximize the clickable link area of the cell. */
    height: 100% !important;
}

/* The headline buttons are positioned in top-right (like the catchup for the feed title).  */
.hl > .left {
    display: grid !important;
    /* Make it a vertical-axis grid. Perhaps should include auto, in case the number of buttons changes (see .pub-pic below.) */
    grid-template-rows: repeat(2, 1fr);
    height: 4rem !important;
    /* First row, third column. */
    grid-row: 1 / 1 !important;
    grid-column: 3 / -1 !important;
    align-items: stretch !important;
    align-content: space-between !important;
}

/* Remove to show the feed button in grid tiles. I don't currently use it, so I hide it. */
.pub-pic {
    display: none !important;
}

/* I prefer the star to be the top item and the checkbox to be under it. */
.hl > .left > .marked-pic {
    order: -1 !important;
}

/*  */
span.hl-content {
    display: grid !important;
    grid-template-columns: 1fr !important;
    grid-template-rows: 1fr auto !important;
    row-gap: 1em !important;
}

/* Show the title which can be multiline.
    The width bit should be removed for more general use. */
span.hl-content > .title {
    display: inline-block !important;
    word-wrap: normal !important;
    white-space: normal !important;
    width: 10em !important;
    grid-row-start: 1 !important;
    grid-row-end: 1 !important;
}

/* Show the full author which can be multiline.
    This style rule goes a bit further than it should, mixing
    with some of my personal style stuff, so may need to be tweaked
    for more general use. (width, line height, particularly.) */
span.hl-content > span.author {
    display: inline-block !important;
    grid-row-start: 2 !important;
    grid-row-end: 2 !important;
    row-gap: 1em !important;
    width: 14em !important;
    line-height: 1em !important;
    word-wrap: normal !important;
    white-space: normal !important;
}

/* Place the score icon and feed icon in the bottom right. */
.hl > .right {
    grid-column: 3 / -1 !important;
    justify-self: end !important;
}

/* Hide these items if they don't contain anything. They are a content preview and the labels. */
span.preview:empty,
span[class^="HLLCTR-"]:empty {
    display: none !important;
}

It’s worth noting I didn’t spend a ton of time cleaning this up, either when I made it or when I decided to post about it, so it may have some rough edges. It didn’t take very long to go from idea to implementation, perhaps a couple of hours, which is a sign of how much easier and more useful CSS has become with things like the grid here. Now, there are things it’s still hard to do with the grid.

For example, without changes to markup, I couldn’t come up with an easy way to make the .feed-title elements fill the rest of the row when there aren’t enough items to do so. I’m pretty sure that could be done if there were separate divs for each feed’s contents, and perhaps it could be done if the grid had pseudo-classes (e.g., :first-row-member), but otherwise I’m not sure how you could do it. (I don’t know if it would have looked better anyway.)

So far I judge it a worthwhile change, as I have found it easier to scan the feeds even if I don’t see all the items at once. It’s more natural for my eyes to jump from item to item, and each one taking up more space gives the illusion that I’m making more progress by looking over each item.

Ink Cream, the New Theme

Finally a fresh coat of paint on this old heap! Sit and read some of how it came to be.

For a while now the site has used the default theme, Twenty Twenty One. I liked it okay, but one thing that bothered me was that the dark mode wasn’t customizable, was kind of bland.

The other thing to know is that I use Stylus to clean up or modify styles on various websites I visit. For some time I’ve been using a semi-dark theme (the same light gray background as the light mode background here) on many news sites, and occasionally a dark version on others (again, same background as the dark theme here). That kind of daily testing led me to find it preferable, and so I wanted to bring that design to this site.

Browsing through Google’s selection, many fonts come close to being great but had at least one or two flaws I couldn’t abide.

The basic premise of the light theme is that websites don’t need to be white-backgrounds. We have shades of gray to use here. And that dark modes don’t need to be white-on-black. Again, we have shades. For the most part, I would be happy to use light themes if they weren’t pure white. All the web designers that shook their fists at the need to create dark variants could have made a compromise, but I haven’t seen any of them do so.

Even at night, the light theme of this site isn’t particularly bad to my eyes, though the dark mode is certainly nicer.


I found underscores.me, a starter theme. It provided a solid base to build on. I later found out that WordPress is soon (sometime next year) to release a “Block theme” built for their new Gutenberg blocks system. I may eventually rebuild for that, or switch to it. It offers “full site editing,” which may kill off traditional themes. I’m not entirely sold yet, but I’m keeping an open mind and waiting to try it out and see what it offers.

I tried to add some functionality with plugins, but they mostly don’t offer an off-switch for their default styles, and trying to dequeue their styles is hit-or-miss, so I built more functionality into the theme itself. If I end up migrating to the new system, I may have to rework that functionality into plugins.

The one decision that skipped a plugin but I didn’t implement myself is code highlighting. After looking at client-side (JavaScript) options and server-side (plugin) options for code highlighting, I decided to go with neither. I can run code through pygmentize when I compose an article, and then I can add the markup directly to the post. I already self-handle other elements, like images. So if I get the itch to actually highlight code, that’s what I’ll do.


One of the big non-technical challenges was finding good typefaces. I opted to use Google Fonts to load nicer typefaces here. (I load them with WebFontLoader.) Browsing through Google’s selection, many fonts come close to being great but had at least one or two flaws I couldn’t abide. One had an inverted at-symbol (@), another had a capital C that looked too much like a G. Most sans-serif fonts don’t properly distinguish between uppercase I and lowercase L.

In the end, I dropped trying to have a separate serif face for headings, in part to keep the site light, and in part because trying to find two faces was too much.

CSS Fun

Having not done much web development in a long while, here are some of the newer things I used in building the stylesheet.

:where()

Having been out of the loop, I didn’t realize :is() and :where() existed. I only use the latter, as it doesn’t influence the scoring of selectors. It lets you do things like:

    .recipe :where(.ingredient, .tool, .container) {
        font-weight: bold;
    }

This saves you creating three separate selectors if you want all “important” items under a recipe to be boldfaced.

clamp()

I knew that math functions existed, but clamp() is really nice. You give it three values:

  • minimum
  • variable
  • maximum

And it will let the outcome vary only within the bounds you set. Most of the time the variable will involve something with vw units, so that the outcome varies based on the screen width, but there are other ways to use it.

Custom property fallbacks

I’ve used custom properties in my user styles for awhile, so that I don’t have to recreate things like colors for every site I create a custom style for. But I didn’t know there are fallbacks. I only use them for filling the SVGs in the social media buttons:

    html:not([dark]) .soc-fill,
    html[dark] .soc-link:focus-visible .soc-fill {
        fill: var(--ic-soc-c0, #000);
    }

    html[dark] .soc-fill,
    html:not([dark]) .soc-link:focus-visible .soc-fill {
        fill: var(--ic-soc-c1, #FFF);
    }

I set fill values (--ic-soc-cN) for some icons, if the service calls for a particular color in their brand guide, but if not I can fallback to black or white (depending on the dark or light theme and whether or not keyboard focus is happening).

Other small bits

Testing in chromium taught me about will-change that hints the browser to expect repaints of an element. It probably isn’t that necessary given it’s only for the theme-mode toggle in the upper right, but it’s cool to know about. I was also able to use a media query for prefers-reduced-motion to stop that animation if someone has a relevant OS setting turned on.


I tried to get accessibility correct. Some of that was helped by WordPress itself, other parts helped by the underscores base. Proper accessibility design helps everyone, including people in early stages of a sight disorder or even someone who’s stressed or drunk or tired.

To improve accessibility, I removed placeholder text in form inputs. After trouble finding good colors that were within the contrast bounds, I did some thinking and reading. I decided that the only real purpose of placeholder text is to make forms look less plain. Multi-field forms need visible labels anyway, and with those the placeholders don’t really add anything to the design.

If you’re relying on hidden labels being read by screen-readers, that leaves people who may have some accessibility needs out in the cold. The exception is single-field forms (like search), where the button next to the field provides enough information for people without screen readers.

I also tried to make keyboard navigation work correctly and with good focus coverage. I actually like how the focus looks more than the lesser design like hovering. I might eventually collapse them into the same design, though I have slight concerns that could be too confusing. Having separate styles for :hover and :focus-visible seems cleaner.

Credits

The darkmode toggle button and its JavaScript is slightly modified from Henry Egloff: “How to Code a Simple Dark Mode Toggle”.

After looking at the options, I went with remedy.css for a “reset” (github: Jen Simmons: cssremedy). I still didn’t use most of it. I’m not targeting Internet Explorer. In general I don’t want to target browsers more than a few years old.

I got a lot of help from the usual:

(Probably forgot some others. It’s the Internet, lots of good and helpful stuff around! Many thanks!)


I’m sure there’s other things I forgot. But that’s enough words for now. Hope visitors enjoy the new look.

Killing Comic Sans

How not to see Comic Sans.

Sure, you could just uninstall the Microsoft core fonts (they are non-free, after all), but they’re nice to have around (I guess?). Or you could just remove Comic Sans itself, but maybe you’ll one day want to use it for good or ill (who knows?) So instead you might turn to fontconfig.

First you might try a substitution rule like:

<alias>
    <family>Comic Sans MS</family>
    <prefer><family>DejaVu Sans</family></prefer>
</alias>

The prefer families specified should (?) be used before using the matched font family, even if it exists. But in testing, that didn’t work for me. Don’t force it, use a bigger hammer.

So I switched to a match/edit rule like:

<match target="font">
    <test name="family" compare="eq" qual="any">
        <string>Comic Sans MS</string>
    </test>
    <edit name="family" mode="assign">
        <string>DejaVu Sans</string>
    </edit>
</match>

This worked, but was too big of a hammer for my taste. For example, in gedit font selection it no longer says, “Comic Sans MS.” It just says, “DejaVu Sans.” What we’re after is substitution of the face, not the whole entry.

As I’m not in the habit of using Comic Sans by choice, the target of the exercise is the web. Ah, but it’s much easier to replace the font for the web. So we walk away from fontconfig and walk over to Stylish (or userstyle.css if you don’t want an add-on to help).

Now we just need a rule that tells the browser, “replace Comic Sans when you see it.” In comes @font-face. We can use this to define, for the browser, what the meaning of a particular font is:

@font-face {
    font-family: "Comic Sans MS";
    src: local("DejaVu Sans");
}

Great! Well, great-ish. We can’t specify the alias “sans-serif” because it’s an alias. That means if you change which font your alias uses (in this case, away from DejaVu Sans), it will require you to update your style rule.

We have limited options here. You could specify the font-weight, but that will interfere with the site’s own weighting. The best case is to use a distinctive replacement font. Or just give up (my choice in this case). Defeating Comic Sans is enough, no need to gloat.