Wednesday, January 18, 2023
HomeProgrammingSolved With :has(): Vertical Spacing in Lengthy-Kind Textual content | CSS-Methods

Solved With :has(): Vertical Spacing in Lengthy-Kind Textual content | CSS-Methods


When you’ve ever labored on websites with a number of long-form textual content — particularly CMS websites the place folks can enter screeds of textual content in a WYSIWYG editor — you’ve possible needed to write CSS to handle the vertical spacing between totally different typographic parts, like headings, paragraphs, lists and so forth.

It’s surprisingly tough to get this proper. And it’s one cause why issues just like the Tailwind Typography plugin and Stack Overflow’s Prose exist — though these deal with far more than simply vertical spacing.

Firefox helps :has() behind the format.css.has-selector.enabled flag in about:config on the time of writing.

What makes typographic vertical spacing sophisticated?

Absolutely it ought to simply be so simple as saying that every component — p, h2, ul, and so forth. — has some quantity of prime and/or backside margin… proper? Sadly, this isn’t the case. Contemplate this desired habits:

  • The primary and final parts in a block of long-form textual content shouldn’t have any further house above or under (respectively). That is in order that different, non-typographic parts are nonetheless positioned predictably across the long-form content material.
  • Sections throughout the long-form content material ought to have a pleasant huge house between them. A “part” being a heading and all the next content material that belongs to that heading. In observe, this implies having a pleasant huge house earlier than a heading… however not if that heading is instantly preceded by one other heading!
Example of a Heading 3 following a paragraph and another following a Heading 2.
We wish to more room above the Heading 3 when it follows a typographic component, like a paragraph, however much less house when it instantly follows one other heading.

You could look no additional than proper right here at CSS-Methods to see the place this might come in useful. Listed here are a few screenshots of spacing I pulled from one other article.

A Heading 2 element directly above a Heading 3.
The vertical spacing between Heading 2 and Heading 3
A Heading 3 element directly following a paragraph element.
The vertical house between Heading 3 and a paragraph

The normal answer

The standard answer I’ve seen includes placing any long-form content material in a wrapping div (or a semantic tag, if acceptable). My go-to class title has been .rich-text, which I believe I exploit as a hangover from older variations of the Wagtail CMS, which might add this class robotically when rendering WYSIWYG content material. Tailwind Typography makes use of a .prose class (plus some modifier lessons).

Then we add CSS to pick all typographic parts in that wrapper and add vertical margins. Noting, after all, the particular habits talked about above to do with stacked headings and the primary/final component.

The normal answer sounds affordable… what’s the issue?

Inflexible construction

Having so as to add a wrapper class like .rich-text in all the correct locations means baking in a particular construction to your HTML code. That’s typically vital, however it feels prefer it shouldn’t need to be on this specific case. It will also be simple to overlook to do that in every single place that you must, particularly if that you must use it for a mixture of CMS and hard-coded content material.

The HTML construction will get much more inflexible while you need to have the ability to trim the highest and backside margin off the primary and final parts, respectively, as a result of they should be speedy kids of the wrapper component, e.g., .rich-text > *:first-child. That > is necessary — in spite of everything, we don’t wish to by accident choose the primary listing merchandise in every ul or ol with this selector.

Mixing margin properties

Within the pre-:has() world, we haven’t had a strategy to choose a component primarily based on what follows it. Subsequently, the normal method to spacing typographic parts includes utilizing a mixture of each margin-top and margin-bottom:

  1. We begin by setting our default spacing to parts with margin-bottom.
  2. Subsequent, we house out our “sections” utilizing margin-top — i.e. very huge house above every heading
  3. Then we override these huge margin-tops when a heading is adopted instantly by one other heading utilizing the adjoining sibling selector (e.g. h2 + h3).

Now, I don’t find out about you, however I’ve at all times felt it’s higher to make use of a single margin path when spacing issues out, usually favoring margin-bottom (that’s assuming the CSS hole property isn’t possible, which it’s not on this case). Whether or not this can be a huge deal, and even true, I’ll allow you to determine. However personally, I’d quite be setting margin-bottom for spacing long-form content material.

Collapsing margins

Due to collapsing margins, this mixture of prime and backside margins isn’t a giant drawback per se. Solely the bigger of two stacked margins will take impact, not the sum of each margins. However… properly… I don’t actually like collapsing margins.

Collapsing margins are but yet another factor to concentrate on. It is likely to be complicated for junior devs who aren’t up to the mark with that CSS quirk. The spacing will completely change (i.e. cease collapsing) should you have been to alter the wrapper to a flex format with flex-direction: column as an example, which is one thing that wouldn’t occur should you set your vertical margins in a single path.

I more-or-less understand how collapsing margins work, and I do know that they’re there by design. I additionally know they’ve made my life simpler now and again. However they’ve additionally made it more durable different occasions. I simply assume they’re kinda bizarre, and I’d usually quite keep away from counting on them.

The :has() answer

And right here is my try at fixing these points with :has().

To recap the enhancements this goals to make:

  • No wrapper class is required.
  • We’re working with a constant margin path.
  • Collapsing margins are prevented (which can or will not be an enchancment, relying in your stance).
  • There’s no setting kinds after which instantly overriding them.

Notes and caveats on the :has() answer

  • All the time test browser help. At time of writing, Firefox solely helps :has() behind an experimental flag.
  • My answer doesn’t embrace all potential typographic parts. As an illustration, there’s no <blockquote> in my demo. The selector listing is straightforward sufficient to increase although.
  • My answer additionally doesn’t deal with non-typographic parts which may be current in your specific long-form textual content blocks, e.g. <img>. That’s as a result of for the websites I work on, we are likely to lock down the WYSIWYG as a lot as potential to core textual content nodes, like headings, paragraphs, and lists. Anything — e.g. quotes, pictures, tables, and so forth. — is a separate CMS part block, and people blocks themselves are spaced aside from one another when rendered on a web page. However once more, the selector listing may be prolonged.
  • I’ve solely included h1 for the sake of completeness. I often wouldn’t permit a CMS person so as to add an h1 by way of WYSIWYG, because the web page title can be baked into the web page template someplace quite than entered within the CMS web page editor.
  • I’m not catering for a heading adopted instantly by the identical degree heading (h2 + h2). This may imply that the primary heading wouldn’t “personal” any content material, which looks as if a misuse of headings (and, right me if I’m fallacious, however it may violate WCAG 1.3.1 Data and Relationships). I’m additionally not catering for skipped heading ranges, that are invalid.
  • I’m on no account knocking the present approaches I discussed. If and after I construct one other Tailwind web site I’ll use the wonderful Typography plugin, no query!
  • I’m not a designer. I got here up with these spacing values by eyeballing it. You in all probability may (and will) use higher values.

Specificity and venture construction

I used to be going to put in writing an entire huge factor right here about how the normal methodology and the brand new :has() manner of doing it’d match into the ITCSS methodology… However now that we now have :the place() (the zero-specificity selector) you possibly can just about select your most well-liked degree of specificity for any selector now.

That mentioned, the truth that we’re not coping with a wrapper — .prose, .rich-text, and so forth. — to me makes it really feel like this could reside within the “parts” layer, i.e. earlier than you begin coping with class-level specificity. I’ve used :the place() in my examples to maintain specificity constant. All of the selectors in each of my examples have a specificity rating of 0,0,1 (aside from the bare-bones reset).

Wrapping up

So there you may have it, a bleeding-edge answer to a really boring drawback! This newer method remains to be not what I’d name “easy” CSS — as I mentioned at first, it’s a extra complicated subject than it may appear at first. However other than having a number of barely complicated selectors, I believe the brand new method makes extra sense general, and the much less inflexible HTML construction appears very interesting.

If you find yourself utilizing this, or one thing prefer it, I’d like to know the way it works out for you. And should you can consider methods to enhance it, I’d love to listen to these too!

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments