Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      10 Benefits of Hiring a React.js Development Company (2025–2026 Edition)

      August 13, 2025

      From Line To Layout: How Past Experiences Shape Your Design Career

      August 13, 2025

      Hire React.js Developers in the US: How to Choose the Right Team for Your Needs

      August 13, 2025

      Google’s coding agent Jules gets critique functionality

      August 13, 2025

      The best smartphones without AI features in 2025: Expert tested and recommended

      August 13, 2025

      GPT-5 was supposed to simplify ChatGPT but now it has 4 new modes – here’s why

      August 13, 2025

      Gemini just got two of ChatGPT’s best features – and they’re free

      August 13, 2025

      I found the easiest way to send files between my Android phone and desktop – and it’s free

      August 13, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Laravel Boost is released

      August 13, 2025
      Recent

      Laravel Boost is released

      August 13, 2025

      Frontend Standards for Optimizely Configured Commerce: Clean & Scalable Web Best Practices

      August 13, 2025

      Live Agent Escalation in Copilot Studio Using D365 Omnichannel – Architecture and Use Case

      August 13, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      OpenAI’s Sam Altman: GPT-5 fails to meet AGI standards amid Microsoft’s fading partnership — “it’s still missing something”

      August 13, 2025
      Recent

      OpenAI’s Sam Altman: GPT-5 fails to meet AGI standards amid Microsoft’s fading partnership — “it’s still missing something”

      August 13, 2025

      You Think You Need a Monster PC to Run Local AI, Don’t You? — My Seven-Year-Old Mid-range Laptop Says Otherwise

      August 13, 2025

      8 Registry Tweaks that will Make File Explorer Faster and Easier to Use on Windows 11

      August 13, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»News & Updates»We Might Need Something Between Root and Relative CSS Units for “Base Elements”

    We Might Need Something Between Root and Relative CSS Units for “Base Elements”

    August 13, 2025

    CSS provides us with root and relative values.

    • Root values are like rem and rlh — they’re tied to the values written in the :root selector (the most common one would be the html element).
    • Relative values are like em, lh, ch and various others — they’re tied to the font-size in that specific element.

    I’ve come to realize that perhaps we need to have a unit between root and relative values. Having such a unit allows us to size things without complex em or lh calculations.

    Let me give you an example: Prose

    Earlier this year, Jen Simmons wrote about the using the lh unit to style margin and padding for better typographical vertical rhythm.

    p { margin-block: 1lh; } 

    We can expand the concept a little further to include all other spaces around the text. One way of doing this is the “Lobotomized Owl” technique that Heydon Pickering popularized a while ago.

    * + * {
      margin-top: 1lh;
    }

    Today, we can also use the :not(:first-child) to achieve the same effect — and that might be a tad more readable.

    *:not(:first-child) {
      margin-top: 1lh;
    }

    Often, we need to constrain these selectors so they don’t spill everywhere and break the rest of the page. One great class for this is .prose.

    .prose {
      *:not(:first-child) {
        margin-top: 1lh;
      }
    }

    This is simple and good — but what happens if you include typography of other sizes…? You’ll see this break down incredibly quickly (because 1lh of a <h2> element can be incredibly big).

    CodePen Embed Fallback

    One way around this issue is to use Flexbox on the parent element. By doing so, we can set gap to 1lh and we don’t have to deal with the value of 1lh changing on the h2 element. (Bonus, we also don’t have to deal with margin collapse.)

    .prose {
      display: flex; 
      flex-direction: column;
      gap: 1lh;
    }
    CodePen Embed Fallback

    But we introduce a new problem here: proximity confusion.

    Content below <h2> belongs within the <h2>. But content above the <h2> belongs with the previous section header. We should, ideally, make the spacing different to clarify their relationship.

    The simplest way is to add a little margin above the <h2>.

    But we can’t add margin above <h2> with lh since the lh value on <h2> will be different from that of the surrounding elements.

    CodePen Embed Fallback

    We have to use a little CSS trickery and margin-bottom (or the logical equivalent) on the element above the <h2>. Here, we simply set margin-bottom to 1lh since we use Flexbox and don’t have to deal with margin collapse. (If you had to deal with margin collapse, you’d have to set margin-bottom to 2lh.)

    CodePen Embed Fallback

    Is there a better way? Well, that’s what this article is about!

    But before we go there, let’s consider a different UI that has similar problems so you can begin to see the greater ramifications of this problem (and the importance of the solution).

    Here’s a second example: Card component

    Now let’s say we have a card component that’s divided into two parts, header and content.

    In these kind of components, the header is often styled with a different font-size than the content.

    Card component example. Large black heading says Card Header. There is a border between the header and a paragraph of placeholder text.

    To create such a card, the simplest markup may be:

    <div class="card">
      <h2 class="title">Card Title</h2>
      <div class="content">Card Content</div>
    </div>

    Unfortunately, we cannot use the lh unit to create the padding within the card — doing so causes the margin on the <h2> element to blow (incredibly) out of proportion!

    CodePen Embed Fallback

    There are, of course, many ways to handle this type of situation.

    One possible way is to change the markup such that the <h2> resides in a <header> element. When we do this, we can apply the padding on the <header>, bypassing the enlarged 1lh problem.

    <div class="card">
      <header class="title">
        <h2>Card Title</h2>
      </header>
      <div class="content">Card Content</div>
    </div>
    CodePen Embed Fallback

    While changing the markup solves the problem, it’s not ideal — since we probably don’t want to create an extra header element unless it’s necessary…

    Well, another possible method is to use a root value like rlh. This allows <h2> and content to use the same base unit, and therefore, create the same padding.

    CodePen Embed Fallback

    But we still run into problems if the .card needs to scale to different font-size values. Imagine you want to make a smaller card — now 1rlh isn’t going to look right since the padding value becomes too big in proportion to the content.

    CodePen Embed Fallback

    What can we do?

    A simple solution is to change the padding value according to the supported variants of the component — but this kinda thing is sorta hard-coded and not very friendly…

    .card-sm { --padding: 0.75rlh; }
    .card-md { --padding: 1rlh; }
    .card-lg { --padding: 1.25rlh; }
    CodePen Embed Fallback

    What’s the alternative?

    This is where an intermediary between root and relative units might come in handy.

    The handy in-between unit

    This section is purely speculative CSS to illustrate a point. We’ll follow up with a simple way to actually do this in practice today in a later section, so hang tight and follow along conceptually for now.

    Let’s say we have a unit that takes it’s reference value from a specified element. We’ll call this a base unit, for lack of a better name.

    • So, 1 base font-size unit could be 1bem.
    • And 1 base line-height unit could be 1blh.

    Pretty easy at this point.

    Imagine we can style the cards with this base unit. Then we can simply use 1blh to quantify the padding and everything else would be sized appropriately:

    .card {
      > * { padding: 1blh; }  
    }
    
    .card-sm { font-size: 0.8em; }
    .card-md { font-size: 1em; }
    .card-lg { font-size: 1.2em; }

    Hurrah?

    Tying this back to the .prose example earlier, it could very well resolve the proximity confusion issue without complicating our selectors:

    .prose {
      h2:not(:first-child) {
        margin-top: 2blh;
      }
    }

    How might this work?

    For this function to be added easily to modern CSS, I could think of two possible ways:

    1. Attach that to container queries.
    2. Define a syntax similar to anchor positioning.

    The container query method

    We already have stuff like cqw and cqh to denote container width and container height values. It’s not too far of a cry to say we could have a cqem (container-query em) unit or cqlh (container-query line-height).

    There are downsides to this approach.

    First, containers need to be defined in a parent element. This requires more markup and makes the code somewhat complex and unintuitive. This code below might be a possible implementation:

    <div class="container">
      <div class="card">
        <h2 class="title">Card Title</h2>
        <div class="content">Card Content</div>
      </div>
    </div>
    .container {
      container-type: inline-size; 
    }
    
    .card {
      > * { padding: 1cqbl; }
    }

    Dealing with nested containers isn’t much a problem, because we can always set the container-name we want to inherit from. But, there might be a collision if we want to use different container references for cqbl and cqw.

    Imagine this:

    <!-- We might want to inherit the cqem or cqbl from here -->
    <div class="container-base">
    
      <!-- But we might need cqw or cqh from here -->
      <div class="two-column-grid"> 
        <div class="card">...</div>
        <div class="card">...</div>
      </div>
    
    </div>

    Kinda sucks to be limited by container collisions.

    Anchor positioning syntax

    In this case, we first decide the base we want to inherit from. We can call this a base-anchor, or something similar.

    Here, we can explicitly set a base anchor name — or perhaps even leave it as none if we don’t wanna name it. Then the rest of the elements within could inherit from this base value immediately:

    .card {
      base-anchor: --card; /* or perhaps none */
      > * { padding: 1blh; }
    }

    If we need to refer to this anchor from a completely unrelated component, we can leverage the anchor name and simply do this:

    .far-away-comp {
      base-name: --card; 
      /* Then use blh from here */
    }

    Double anchor

    One fascinating aspect I can think of is a potential double-anchor use case where the base component is able to inherit its font-size or value from yet another base or its parent element.

    This flexibility lets us create component variations based on font sizes incredibly easily without needing to rely on complex em calculations.

    Here’s an example of what I’m talking about:

    .prose {
      base-anchor: --prose;
      font-size: 1em;
      line-height: 1.5;
    }
    
    /* Inherits font-size from .prose */
    /* This is automatic if base-name is not provided */
    .card {
      base-anchor: none;
      base-name: --prose;
    
      /* In this case, 1blh could be 1.5em */
      > * { padding: 1blh; }
    }
    
    /* After inheriting the font size, since we have a base-anchor in the card, we adjust the font-size value accordingly, so: 
      - 1bem would mean 0.8em further in the card
      - 1blh could then mean 0.8 * 1.5em = 1.2em 
    */
    .card.card-sm {
      font-size: 0.8em;
    }

    Fascinating, yeah? This brings about a whole new possibility when creating reusable components.

    Putting it into practice today

    Let me preface this section with the fact that bem and blh does not exist today. So whatever implementation I can come up with is simply an imperfect stop-gap measure.

    Today, we are certain that we can use the em unit for such a purpose — but this requires a little bit more calculation, since em is a relative, not a base unit.

    The first step is to determine the base element — and the base font size — which we can do by setting the base-size property:

    .card { 
      --base-size: 1em; 
      font-size: var(--base-size);
    }
    
    .card-sm { 
      --base-size: 0.8em; 
    }

    We can then simulate the bem (base em) unit by dividing the intended font-size with the base-size:

    .card {
      h2 {
        --font-size: 2em;
        font-size: calc(var(--font-size) / var(--base-size));  
      }
    }

    Unfortunately, the above code won’t work because we can’t perform a calc() division with a unit-ed value. So the best we can do to remove the units from --base-size.

    When we do this, we need to perform another calc() on the base element to create the actual font-size property:

    .card { 
      --base-size: 1; 
      font-size: calc(var(--base-size) * 1em);
    }

    Then we perform the same calc() in the <h2> to create its font size:

    .card {
      h2 {
        --font-size: 2;
        font-size: calc(var(--font-size) / var(--base-size) * 1em);  
      }
    }

    This is all starting to get a little “ugh”.

    Nobody wants to all these boilerplate code. So, this is best abstracted away with a mixin, or perhaps even a function. If you use Sass, you might imagine something like this:

    @mixin base-anchor() {
      font-size: calc(var(--base-size) * 1em);
    }

    If you use Tailwind, perhaps you can imagine the Tailwind utility to do the same. After all, Tailwind utilities can be seen as convenient Sass mixins.

    @utility base-anchor {
      font-size: calc(var(--base-size) * 1em);
    }

    We can then apply this utility into the base element. The code looks a little bit cleaner:

    .card {
      @apply base-anchor; 
      --base-size: 1; 
    }
    
    .card-sm { --base-size: 0.8; }

    For the <h2>, we can create another utility to perform the calculation for us. It’ll look something like this:

    @utility text-relative {
      font-size: calc(var(--text-size) / var(--base-size) * 1em);
    }

    We can then use the utility like this:

    .card .title {
      @apply text-relative;
      --text-size: 2; 
    }

    Now, to calculate the padding of the card for the .title element, we need to reverse the font-size to get the base-size value. This is best done with a CSS function, which is not widely supported today, but hopefully, soon!

    @function --bem(--multiplier) {
      result: calc(var(--text-size / var(--base-size) * 1em * --multiplier));
    }

    We can then use --bem to calculate the padding on the card title:

    .card .title {
      /* ... */
      padding-block: --bem(0.5);
      padding-inline: --bem(1);
    }

    We mentioned above that the lh value works better for margin and padding since it preserves vertical rhythm. So, why not create a --blh function too?

    In this case, we can add a --leading variable that the function can inherit from:

    @function --blh(--multiplier, --lh-multiplier) {
      result: calc(
        var(
          --text-size / var(--base-size) * 1em * --multiplier *
            var(--lh-multiplier, var(--leading))
        )
      );
    }

    Then we can use --blh like this:

    .card .title {
      /* ... */
      padding-block: --blh(0.5);
      padding-inline: --blh(1);
    }

    In the spirit of today

    We can’t use --bem and --blh in production because CSS Functions are not available all browsers yet. In the spirit of making bem work right now, we can create a utility, that calculates the --base-font-size from the --font-size.

    Notice this new variable is called --base-font-size, not --base-size, since --base-size is already used. (We cannot overwrite the CSS variable.)

    /* I multiplied the value by 1em here to make it easy for you to use the value */
    @utility base-font-size {
      --base-font-size: calc(var(--base-size) / var(--font-size) * 1em);
    }

    We can also create a utility called base-line-height to get the value of the line-height. When we do this, it’s much easier if we also pass in a --leading variable:

    @utility base-line-height {
      --base-leading: calc(var(--base-font-size)* var(--leading));
    }

    Then we can use calc on --base-leading to get the values we want:

    .card .title {
      @apply text-relative;
      @apply base-font-size;
      @apply base-line-height;
      --font-size: 2; 
      padding-inline: var(--base-line-height);
      padding-block: calc(var(--base-line-height) * 0.5);
    }

    Putting it all together

    Let’s first put together the necessary utilities and functions to make this happen today:

    /* The necessary utilities */
    @utility base-anchor {
      font-size: calc(var(--base-size) * 1em);
    }
    
    @utility text-relative {
      font-size: calc(var(--font-size) / var(--base-size) * 1em);
    }
    
    /* To use this today */
    @utility base-font-size {
      --base-font-size: calc(var(--base-size) / var(--font-size) * 1em);
    }
    
    @utility base-line-height {
      --base-line-height: calc(var(--base-font-size)* var(--leading));
    }
    
    /* Easier usage when CSS Functions become available */
    @function --bem(--multiplier) {
      result: calc(var(--font-size / var(--base-size) * 1em * --multiplier));
    }
    
    @function --blh(--multiplier, --lh-multiplier) {
      result: calc(
        var(
          --font-size / var(--base-size) * 1em * --multiplier *
            var(--lh-multiplier, var(--leading))
        )
      );
    }

    Now here’s the .card code to achieve the functionality in Tailwind we were talking about. You can see it at work here.

    /* What we can actually use today */
    .card {
      @apply base-anchor;
      --base-size: 1;
      --leading: 1.5;
      
      > * { padding: 1lh; }
    
      .title {
        @apply text-relative;
        @apply base-font-size;
        @apply base-line-height;
        --font-size: 2; 
        padding-inline: var(--base-line-height);
        padding-block: calc(var(--base-line-height) * 0.5);
      }
    }
    
    .card-sm {
      --base-size: 0.8;
      .title {
        --font-size: 1.2;
      }
    }
    
    /* What we can use when CSS Functions are available */
    .card {
      @apply base-anchor;
      --base-size: 1;
      
      > * { padding: calc(--blh(1)); }
    
      .title {
        @apply text-relative;
        --text-size: 2; 
        padding-block: calc(--blh(0.5));
      }
    }

    It’s still not as pretty as the bem and blh versions I’ve shown you above, but at the very least, we achieve some sort of functionality, yeah? And it doesn’t look half bad!

    Using this with Splendid Labz today

    Splendid Styles — the branch of Splendid Labz that handles design and styles — contains the code you can use today.

    We also included the --bem and --blh versions if you wanna play with them as well.

    To use Splendid Styles, just download the library, import the base-font-size file, and do what you’ve just seen the above!

    npm i @splendidlabz/styles
    @import '@splendidlabz/styles/typography/base-font-size.css'

    That’s it!

    Now, if you’re interested in all of the tools I’m been cooking up to make web development simpler, you can grab an early bird discount for the Splendid Pro package today — this is available to all CSS-Tricks readers!

    (I might add a lifetime option to the Styles package as it evolves to sufficiently. But it might be a year or so before that happens.)

    Alright, enough promotion. Let’s come back here.

    What do you think about this unit between root and relative values?

    I hesitate to call it “base” em because “base” can mean so many things. But it also sounds right at the same time.

    • Does bem and blh make sense to you?
    • Do you think I’m thinking a wee bit too much for this design aspect?
    • Maybe you’ve got a better name for this?

    I’d love to hear from you so please feel free to share your thoughts below!


    We Might Need Something Between Root and Relative CSS Units for “Base Elements” originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    Source: Read More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleMicrosoft Targets ‘Critical AI Talent’ from Meta to Dominate Next AI Breakthroughs
    Next Article Pyrefly – Python type checker

    Related Posts

    News & Updates

    The best smartphones without AI features in 2025: Expert tested and recommended

    August 13, 2025
    News & Updates

    GPT-5 was supposed to simplify ChatGPT but now it has 4 new modes – here’s why

    August 13, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    Is your phone spying on you? | Unlocked 403 cybersecurity podcast (S2E5)

    Development

    I was skeptical about big-screen laptops, but this Acer model is my new go-to for work

    News & Updates

    Breaking Boundaries: Dadi’s Incredible Journey to a PhD in AGI

    Artificial Intelligence

    CVE-2025-4577 – Smash Balloon Social Post Feed – WordPress Stored Cross-Site Scripting Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    Highlights

    CVE-2025-2766 – 70mai A510 Default Password Authentication Bypass

    June 6, 2025

    CVE ID : CVE-2025-2766

    Published : June 6, 2025, 7:15 p.m. | 33 minutes ago

    Description : 70mai A510 Use of Default Password Authentication Bypass Vulnerability. This vulnerability allows network-adjacent attackers to bypass authentication on affected installations of 70mai A510. Authentication is not required to exploit this vulnerability.

    The specific flaw exists within the default configuration of user accounts. The configuration contains default password. An attacker can leverage this vulnerability to bypass authentication and execute arbitrary code in the context of the root. Was ZDI-CAN-24996.

    Severity: 8.8 | HIGH

    Visit the link for more details, such as CVSS details, affected products, timeline, and more…

    Llama 4 family of models from Meta are now available in SageMaker JumpStart

    April 7, 2025

    This 4K laser projector delivers glorious visuals that rival traditional TVs – here’s my buying advice

    May 29, 2025

    How to Create Documentation with docs.page – A Beginner’s Tutorial

    May 6, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.