My Blog Theme

I use a custom Sphinx theme for this site and as I was tweaking some of the CSS recently I realised that it would be great if I could use awdur to explain to myself how various parts of the site are implemented.

Well after a quick PR adding support for injecting files into a Sphinx build this became possible! This somewhat long page is dedicated to all the CSS powering this site, maybe someday I can extend it to cover the HTML templates and JS too…

CSS Variables

Below are the CSS Variables used throughout the site’s theme.

_static/css/styles.css
sphinx:dirhtml r1
:root {
    --bg-main: white;
    --fg-main: black;

    --bg-sidebar: #161b22;
    --fg-sidebar: white;
    --fg-sidebar-dim: #aaa;

    --highlight-color: #f0b100;
    --border-color: #657b83;

    --fg-accent-dim: #064e3b;
    --fg-accent: #059669;
    --fg-accent-bright: #a7f3d0;

    --transition: 300ms;

    --sidebar-min-width: 0.5em;
    --sidebar-max-width: 300px;

    --panel-min-height: 0.5em;
    --panel-max-height: 400px;

    /* Closed by default */
    --left-sidebar-width: var(--sidebar-min-width);
    --right-sidebar-width: var(--sidebar-min-width);
    --panel-height: var(--sidebar-min-width);
}

Which makes implementing a dark theme straightforward

_static/css/styles.css
sphinx:dirhtml r1
@media(prefers-color-scheme: dark) {
  :root {
      --bg-main: #0d1117;
      --fg-main: white;

      --border-color: #839496;

      --fg-accent-dim: #064e3b;
      --fg-accent: #059669;
      --fg-accent-bright: #a7f3d0;
  }
}

As well as respecting the prefers-reduced-motion preference

_static/css/styles.css
sphinx:dirhtml r1
@media screen and (prefers-reduced-motion: reduce) {
  :root {
    --transition: 0.001ms;
  }
}

A CSS “Reset”

You might have heard of a CSS reset, while I haven’t yet felt the need for a full blown reset stylesheet I do include the following

_static/css/styles.css
sphinx:dirhtml r1
* {
  margin: 0;
  box-sizing: border-box;
}

Utility Classes

.guilabel

Used by the :guilabel: role.

_static/css/styles.css
sphinx:dirhtml r1
.guilabel {
  color: var(--fg-accent);
  font-style: italic;
  font-weight: bold;
}

.hidden

Easy way to hide elements.

_static/css/styles.css
sphinx:dirhtml r1
.hidden { display: none; }

.highlighted

Used by the search system to highlight matched terms.

_static/css/styles.css
sphinx:dirhtml r1
.highlighted {
  background: var(--highlight-color);
  color: var(--bg-main);
  padding: 0 0.5em;
  border: solid 1px var(--fg-main);
  border-radius: 3px;
}

.line-through

On some pages I define an ad-hoc role to strikethrough some text.

sphinx:dirhtml r1
.. role:: strike
   :class: line-through

Which requires the CSS for the given class name to be defined.

_static/css/styles.css
sphinx:dirhtml r1
.line-through { text-decoration: line-through; }

HTML Elements

Trying to work with the CSS cascade rather than against it, it’s good to try and define global rules that apply to all HTML elements

Adding styles to the html element applies them to everything on the page. I also think that by setting the font size here, I can use rem units to set all font sizes relative to this base size.

_static/css/styles.css
sphinx:dirhtml r1
html {
  font-size: 13pt;
  font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  scroll-behavior: smooth;
}

a

It’s nice if links follow the accent color

_static/css/styles.css
sphinx:dirhtml r1
a { color: var(--fg-accent);}

Tags

When linking to tags I want the link to be styled like a “pill”.

_static/css/styles.css
sphinx:dirhtml r1
a.tag {
  color: var(--fg-accent-bright);
  background: var(--fg-accent-dim);
  border: solid 1px var(--fg-accent);
  border-radius: 3px;
  padding: 0.1em 0.5em;

  span.count {
    color: var(--fg-main);
  }
}

main a.tag {
  color: var(--bg-main);
  background: var(--fg-accent);
  border-color: var(--fg-accent-dim);
}

Lists of tags should be flattened

_static/css/styles.css
sphinx:dirhtml r1
ul.taglist {
  display: flex;
  gap: 1em;
  flex-wrap: wrap;

  li {
    list-style: none;
    margin: 0;
  }
}

blockquote

As generated by the .. pull-quote:: directive.

The technology you use impresses no one.

The experience you create with it is everything. – Sean Gerety

_static/css/styles.css
sphinx:dirhtml r1
blockquote.pull-quote {
    padding: 0 1em;
    font-style: italic;
    border-left: solid var(--fg-accent);
}

details

Expand...

For additional information.

_static/css/styles.css
sphinx:dirhtml r1
details > :not(summary) {
  border-left: solid 1px var(--border-color);
  padding: 0 0 0 1em;
  margin: 0 0.2em;
}

dd & dt

Used by Sphinx/docutils to represent definition lists

dt

Represents the term to be defined.

dd

Contains the definition.

_static/css/styles.css
sphinx:dirhtml r1
dt { margin-top: 1em; }

dd {
  margin-left: 1em;
  padding-left: 1em;
  border-left: solid 1px var(--fg-accent);

  p:first-child {
    margin-top: 0;
  }
}

figure

_static/css/styles.css
sphinx:dirhtml r1
figure {
  &.align-center img {
    display: block;
    margin: auto;
  }

  figcaption p {
    margin: auto;
    font-size: 0.8rem;
    text-align: center;
  }
}

h1-h6

_static/css/styles.css
sphinx:dirhtml r1
h1, h2, h3, h4, h5, h6 {
  font-weight: 500;
}

h1 { font-size:   2rem; }
h2 { font-size: 1.8rem; }
h3 { font-size: 1.7rem; }
h4 { font-size: 1.5rem; }
h5 { font-size: 1.4rem; }
h6 { font-size: 1.2rem; }

img

_static/css/styles.css
sphinx:dirhtml r1
img { max-width: 100%; }

kbd

e.g. C-x C-f

_static/css/styles.css
sphinx:dirhtml r1
kbd {
  padding: 0 0.15em;
  color: var(--fg-accent);
}

table

A table is composed of many elements.

Element

Description

table

Top-level table element

thead

Contains the table’s header rows

tbody

Contains the table’s rows

tr

Defines a row

th

Defines a header cell

td

Defines a normal cell

_static/css/styles.css
sphinx:dirhtml r1
table {
  width: 100%;
  border-collapse: collapse;

  thead {
    tr {
      border-bottom: solid 1px var(--border-color);
    }
  }

  td, th {
    padding: 0 0.5em;
    border-right: 1px solid var(--border-color);
  }

  td:last-child, th:last-child {
    border-right: none;
  }
}

Layout

The layout for this site is divided into the following sections

Header Left Main Right Footer

I want a somewhat dynamic layout for this site, meaning that the sidebars and the footer should be open/closable by the user.

Amazingly, it turns out that the grid-template-columns and grid-template-rows CSS properties can be animated! So as long as you set the transition property and use a few CSS variables

_static/css/styles.css
sphinx:dirhtml r1
body { overflow: hidden; }

.grid {
  color: var(--fg-main);
  background: var(--bg-main);

  display: grid;
  grid-template-columns: var(--left-sidebar-width) 1fr var(--right-sidebar-width);
  grid-template-rows: 3em calc(100vh - 3em - var(--panel-height)) var(--panel-height);
  transition: var(--transition);
}

Then the standard checkbox trick can be used to open/close the various elements! For example, assuming that when checked the sidebar should close, the CSS for the left sidebar might look something like this.

sphinx:dirhtml r1
#left-sidebar-checkbox {
  &:checked ~ .grid {
    --left-sidebar-width: var(--sidebar-min-width);

    .sidebar {
      opacity: 0;
    }
  }
}

Which, assumes the following HTML structure.

sphinx:dirhtml r1
<body>
  <input type="checkbox" id="left-sidebar-checkbox" class="hidden" />
  <div class="grid">
     ...
     <aside>
       <section class="left-sidebar-toggle">
         <label for="left-sidebar-checkbox"></label>
       </section>
       <div class="sidebar">...</div>

However, I think the only way to make this work across screen sizes, is to change the meaning of the checkbox state depending on the screen size. This is bound to lead to some quirks if you resize the screen across a breakpoint, but hopefully, it’s enough of an edge case to not have to worry too much about it! 😅

Main

_static/css/styles.css
sphinx:dirhtml r1
body { background: var(--bg-sidebar); }

main {
  min-width: 0;
  max-height: 100%;
  overflow-y: auto;
}

article

I use article elements to contain the main content of the page.

_static/css/styles.css
sphinx:dirhtml r1
article {
  padding: 0 2em;
  line-height: 1.5;
  max-width: 100ch;

  footer {
    border-top: solid 1px var(--border-color);
  }
}

On mobile the padding should be reduced a bit to create more space

_static/css/styles.css
sphinx:dirhtml r1
@media screen and (max-width: 600px) {
  article { padding: 0 0.5em; }
}

Admonitions

_static/css/styles.css
sphinx:dirhtml r1
div.admonition {
  margin: 1em 0;
  border: solid 1px var(--border-color);
  border-radius: 3px;

  & > :not(ol,ul) {
    padding: 0.5em;
    margin: 0;
  }

  .admonition-title {
    margin: -1px -1px 0 -1px;
    color: var(--fg-accent);
    border: solid 2px var(--fg-accent);
    border-left: solid 5px var(--fg-accent);
    border-top-left-radius: 3px;
    border-top-right-radius: 3px;
  }
}

Bib

Styles for elements from the local bib Sphinx extension used on this site.

_static/css/styles.css
sphinx:dirhtml r1
dl.bib {
  font-size: 12pt;
  margin: 2em 0;
  padding: 0;

  display: grid;
  grid-template-columns: 115px auto;
  grid-template-rows: 150px auto;

  & > dd {
    grid-column: span 2;
    margin: 0;
    margin-top: 1em;
    padding: 0.5em;

    &:empty {
      padding: 0;
      margin-top: 0;
    }
  }

  dt {
    display: grid;
    align-items: baseline;
    align-content: end;
    grid-template-columns: auto 0.5em 1fr;
    margin: 0;
    padding: 0.5em;
  }

  .sig-name {
    font-size: 1.25em;
    text-wrap: balance;
  }

  .bib-state span.pre {
    display: inline-block;
    color: var(--fg-accent-bright);
    background: var(--fg-accent);
    padding: 0 0.5em;
    border: solid 1px var(--fg-accent-bright);
    border-radius: 2px;
    font-style: normal;
    text-transform: uppercase;
  }

  .bib-image {
    height: 150px;
    background: white;

    img {
      max-height: 100%;
      width: 100%;
      object-fit: cover;
      object-position: top center;
    }
  }
}

Need to consider narrow displays as well

_static/css/styles.css
sphinx:dirhtml r1
@media screen and (max-width: 800px) {
  dl.bib {
    grid-template-columns: auto;

    & > dd {
      grid-column: unset;
    }
  }
}

Code

Sphinx/docutils render inline code in <code> tags with the text further wrapped in a <span> tag.

_static/css/styles.css
sphinx:dirhtml r1
article {
  code.literal {
    &::before, &&::after {
      content: "`";
    }

    span.pre {
      font-weight: 600;
      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
    }
  }
}

While code blocks are in a <div class="highlight"> tag.

_static/css/styles.css
sphinx:dirhtml r1
div.highlight {
  padding: 0.5em;
  margin: 0.5em 0;
  overflow-x: auto;
  border: solid 1px var(--border-color);
  border-radius: 5px;
}

I use awdur throughout this site to take the contents of code blocks and export them to separate code files (for example all the CSS on this page!). Awdur provides a code block header, indicating where the code will be exported to. This header also needs to be styled to match the look and feel of this site.

_static/css/styles.css
sphinx:dirhtml r1
div.awdur-codeblock {
  border: none;

  div.awdur-codeblock-header {
    color: var(--fg-sidebar);
    background: var(--bg-sidebar);
    padding: 0 0.5em;

    display: flex;
    justify-content: space-between;

    border: solid 1px var(--border-color);
    border-top-left-radius: 5px;
    border-top-right-radius: 5px;
    border-bottom: none;
  }

  div.highlight {
    border-top: none;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
    margin-top: 0;
  }
}

Typography

_static/css/styles.css
sphinx:dirhtml r1
article {
  section {margin-top: 2em;}

  h1, h2, h3, h4, h5, h6 { color: var(--fg-accent); }

  p { margin: 1em 0; }

  table {
    line-height: unset;
    p { margin: 0;}
  }
}

Post Collections

I quite liked the idea of placing all of my blog posts on a timeline, but it does involve a fair amount of CSS. I keep telling myself that I’m going to expand the type of “events” in the list… someday! 😅

_static/css/styles.css
sphinx:dirhtml r1
article.summary {
  display: grid;
  grid-template-columns: 250px auto;

  & > :not(aside) {
    padding: 0 1em;
  }

  header, footer {
    grid-column: 2;
  }

  header {
    position: relative;

    &::before {
      content: '';
      width: 0.75em;
      height: 0.75em;
      background: var(--border-color);
      border-radius: 100%;
      position: absolute;
      top: 1em;
      left: -0.4em;
    }
  }

  aside {
    grid-row: 1 / span 3;
    border-right: solid 1px var(--border-color);
    padding: 0 1em;

    section.post-metadata {
      margin-top: 0.4em;
      align-items: flex-start;

      h5 { display: none; }

      ul.taglist { gap: 0.5em; }
    }
  }

  footer {
    border-top: none;
    text-align: right;
    margin-bottom: 4em;
  }
}

Of course on smaller displays, things need to be rearranged a bit.

_static/css/styles.css
sphinx:dirhtml r1
@media screen and (max-width: 1200px) {
  article.summary {
    grid-template-columns: auto;

    header, footer { grid-column: unset; }
    header::before { display: none; }

    aside {
      grid-row: unset;
      border: none;
    }

  }
}

It would be interesting to see if I can replace the above with a container query at some point.

Search Results

The search results page requires its own set of rules.

_static/css/styles.css
sphinx:dirhtml r1
section#search-results {
  padding: 2em;

  h2 { color: var(--fg-accent); }

  p.search-summary {
    padding: 1em 0;
  }

  ul.search {
    padding: 0;

    li {
      margin: 1em 0;
      padding: 1em;
      list-style: none;
    }

    p.context {
      margin: 0.5em 0;
    }
  }
}

Index Pages

Covers pages like #emacs

_static/css/styles.css
sphinx:dirhtml r1
section.domainindex {
  padding: 2em;

  h1 { color: var(--fg-accent); }

  div.modindex-jumpbox {
     display: grid;
     grid-template-columns: repeat(auto-fit, minmax(20px, 1fr));
     justify-items: center;
     margin: 1em 0;
  }
}

On mobile the padding should be reduced a bit to create more space

_static/css/styles.css
sphinx:dirhtml r1
@media screen and (max-width: 600px) {
  section.domainindex { padding: 0.5em; }
}

Footer

_static/css/styles.css
sphinx:dirhtml r1
footer.site {
  color: var(--fg-sidebar);
  background: var(--bg-sidebar);

  grid-column: span 3;
}