DEV Community

Abdurrahman Fadhil
Abdurrahman Fadhil

Posted on • Originally published at rahmanfadhil.com

Some CSS tricks I found useful

Originally posted on my blog.

It's been a while since I've done any software development. I miss the good old days when I could just sit down and build stuff, without having to worry about consumer optimization problems and ordinary least squares. So, I updated my blog, a static site generated by Hugo. No JavaScript frameworks, no pre-processors. Just markdown, HTML, and CSS. This constraint forced me to relearn modern CSS, and it's quite remarkable to see how much can be accomplished with CSS alone today.

I wanted to share a few techniques I've implemented. These aren't groundbreaking inventions, but they are elegant solutions (I think) to common web design problems.

Custom CSS properties for theming

Dark themes are somewhat expected these days. To implement automatic light and dark mode theming that respects your operating system preference, I use CSS variables (custom properties) and the prefers-color-scheme media query.

The approach is straightforward. First, define all colors as variables in the :root selector. These will be the default (light theme) values.

:root {
  /* Light Theme (Default) */
  --color-bg: oklch(96.7% 0.001 286.375);
  --color-text: oklch(14.1% 0.005 285.823);
  --color-border: oklch(87.1% 0.006 286.286);
  --color-link: #0369a1;
  --color-card-bg: oklch(100% 0 0);
  /* ... more variables */
}
Enter fullscreen mode Exit fullscreen mode

Then, use a media query to check if the user prefers a dark color scheme. If they do, simply redefine the same set of variables with their dark-theme equivalents.

@media (prefers-color-scheme: dark) {
  :root {
    /* Dark Theme Overrides */
    --color-bg: #0d1117;
    --color-text: #c9d1d9;
    --color-border: #30363d;
    --color-link: #58a6ff;
    --color-card-bg: #161b22;
    /* ... more variables */
  }
}
Enter fullscreen mode Exit fullscreen mode

Everywhere else in the stylesheet, colors are referenced using these variables (e.g., background-color: var(--color-bg);). This centralizes theme management and makes the site adapt automatically without a single line of JS.

Home page light vs dark

Blog post page light vs dark

Mixing colors with color-mix()

color-mix() allows you to programmatically mix two colors. I use this to generate my link hover and underline colors directly from the base link and background colors.

:root {
  --color-link: #0369a1;
  --color-link-hover: color-mix(in srgb, var(--color-link) 75%, #000 25%);
  --color-link-underline: color-mix(
    in srgb,
    var(--color-link) 50%,
    var(--color-bg)
  );
}
Enter fullscreen mode Exit fullscreen mode

Here, --color-link-hover is created by mixing the base link color with black, and the underline color is a 50/50 mix of the link color and the background color. If I change --color-link, the hover and underline states update automatically. This makes the color system more relational and easier to maintain.

Automatic numbering with CSS counters

For technical articles, numbered headings are essential for structure and readability. Instead of manually numbering them in Markdown, which is tedious and error-prone, you can automate it entirely with CSS Counters.

The logic involves three steps:

  • Reset a counter on a parent element (article).
  • Increment the counter on the heading you want to number (h2).
  • Display the counter using a ::before pseudo-element.

Here's how it's implemented for headings:

article {
  counter-reset: h2 figure; /* Reset h2 and figure counters */
}

article h2 {
  counter-increment: h2;
  counter-reset: h3; /* Reset h3 when we see a new h2 */
}

article h3 {
  counter-increment: h3;
}

article h2::before {
  content: counter(h2) ".";
}

article h3::before {
  content: counter(h2) "." counter(h3) ".";
}
Enter fullscreen mode Exit fullscreen mode

This system nests perfectly, creating section numbers like 1., 1.1., 1.2., etc., all automatically. The same technique is used for figures, incrementing a separate figure counter.

The "full-bleed" card component

A common design pattern is to have a main content column with a set width, but to have certain elements (like images or cards) break out of that container to span the full width of the viewport, especially on mobile.

My .card component does this on smaller screens. The layout is based on a .container class that sets a max-width and padding-inline.

.container {
  max-width: var(--container-max-width);
  margin-inline: auto;
  padding-inline: var(--container-padding);
}
Enter fullscreen mode Exit fullscreen mode

The card is normally constrained by this padding. However, in the mobile media query we apply a negative margin equal to the container's padding.

@media (max-width: 768px) {
  .card {
    /* This pulls the card out to the viewport edges */
    margin-inline: calc(-1 * var(--container-padding));

    /* We then remove the side borders that are now off-screen */
    border-inline: none;
  }
}
Enter fullscreen mode Exit fullscreen mode

The calc(-1 * var(--container-padding)) perfectly cancels out the container's padding, making the card "bleed" to the edges of the screen.

Card component on desktop (contained) and mobile (full-bleed)

Selecting parent elements with :has()

The :has() pseudo-class, often called the "parent selector," is a game-changer. It allows you to style an element based on what it contains. This solves many problems that previously required JavaScript or brittle class names.

For example, on my blog page, blog post titles in cards are <h1> tags. But on the post's actual page, the <h1> is the main title. I wanted the card titles to be smaller. With :has(), I can style an h1 differently only if it contains a link (<a>), which is true for cards but not the main article title.

article.card h1:has(a) {
  font-size: var(--text-h2);
}
Enter fullscreen mode Exit fullscreen mode

This rule reads: "Select any <h1> inside an article.card that has an <a> element as a child, and apply a smaller font size to it." This is far more elegant than adding a special class (.post-list-title). It allows you to style elements based on hierarchy and content rather than just class names.

It's exciting to see how much can be achieved with pure CSS these days. Less truly is more.

Top comments (1)

Collapse
 
kemist80 profile image
kemist80 • Edited

Despite semantically more than one h1 inside the same section is not recommended I found it useful.