Tag: Text

Solved With :has(): Vertical Spacing in Long-Form Text

If you’ve ever worked on sites with lots of long-form text — especially CMS sites where people can enter screeds of text in a WYSIWYG editor — you’ve likely had to write CSS to manage the vertical spacing between different typographic elements, like headings, paragraphs, lists and so on.

It’s surprisingly tricky to get this right. And it’s one reason why things like the Tailwind Typography plugin and Stack Overflow’s Prose exist — although these handle much more than just vertical spacing.

Firefox supports :has() behind the layout.css.has-selector.enabled flag in about:config at the time of writing.

What makes typographic vertical spacing complicated?

Surely it should just be as simple as saying that each element — p, h2, ul, etc. — has some amount of top and/or bottom margin… right? Sadly, this isn’t the case. Consider this desired behavior:

  • The first and last elements in a block of long-form text shouldn’t have any extra space above or below (respectively). This is so that other, non-typographic elements are still placed predictably around the long-form content.
  • Sections within the long-form content should have a nice big space between them. A “section” being a heading and all the following content that belongs to that heading. In practice, this means having a nice big space before a heading… but not if that heading is immediately preceded by another heading!
Example of a Heading 3 following a paragraph and another following a Heading 2.
We want to more space above the Heading 3 when it follows a typographic element, like a paragraph, but less space when it immediately follows another heading.

You need to look no further than right here at CSS-Tricks to see where this could come in handy. Here are a couple of screenshots of spacing I pulled from another 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 space between Heading 3 and a paragraph

The traditional solution

The typical solution I’ve seen involves putting any long-form content in a wrapping div (or a semantic tag, if appropriate). My go-to class name has been .rich-text, which I think I use as a hangover from older versions of the Wagtail CMS, which would add this class automatically when rendering WYSIWYG content. Tailwind Typography uses a .prose class (plus some modifier classes).

Then we add CSS to select all typographic elements in that wrapper and add vertical margins. Noting, of course, the special behavior mentioned above to do with stacked headings and the first/last element.

The traditional solution sounds reasonable… what’s the problem?

Rigid structure

Having to add a wrapper class like .rich-text in all the right places means baking in a specific structure to your HTML code. That’s sometimes necessary, but it feels like it shouldn’t have to be in this particular case. It can also be easy to forget to do this everywhere you need to, especially if you need to use it for a mix of CMS and hard-coded content.

The HTML structure gets even more rigid when you want to be able to trim the top and bottom margin off the first and last elements, respectively, because they need to be immediate children of the wrapper element, e.g., .rich-text > *:first-child. That > is important — after all, we don’t want to accidentally select the first list item in each ul or ol with this selector.

Mixing margin properties

In the pre-:has() world, we haven’t had a way to select an element based on what follows it. Therefore, the traditional approach to spacing typographic elements involves using a mix of both margin-top and margin-bottom:

  1. We start by setting our default spacing to elements with margin-bottom.
  2. Next, we space out our “sections” using margin-top — i.e. very big space above each heading
  3. Then we override those big margin-tops when a heading is followed immediately by another heading using the adjacent sibling selector (e.g. h2 + h3).

Now, I don’t know about you, but I’ve always felt it’s better to use a single margin direction when spacing things out, generally favoring margin-bottom (that’s assuming the CSS gap property isn’t feasible, which it is not in this case). Whether this is a big deal, or even true, I’ll let you decide. But personally, I’d rather be setting margin-bottom for spacing long-form content.

Collapsing margins

Because of collapsing margins, this mix of top and bottom margins isn’t a big problem per se. Only the larger of two stacked margins will take effect, not the sum of both margins. But… well… I don’t really like collapsing margins.

Collapsing margins are yet one more thing to be aware of. It might be confusing for junior devs who aren’t up to speed with that CSS quirk. The spacing will totally change (i.e. stop collapsing) if you were to change the wrapper to a flex layout with flex-direction: column for instance, which is something that wouldn’t happen if you set your vertical margins in a single direction.

I more-or-less know how collapsing margins work, and I know that they’re there by design. I also know they’ve made my life easier on occasion. But they’ve also made it harder other times. I just think they’re kinda weird, and I’d generally rather avoid relying on them.

The :has() solution

And here is my attempt at solving these issues with :has().

To recap the improvements this aims to make:

  • No wrapper class is required.
  • We’re working with a consistent margin direction.
  • Collapsing margins are avoided (which may or may not be an improvement, depending on your stance).
  • There’s no setting styles and then immediately overriding them.

Notes and caveats on the :has() solution

  • Always check browser support. At time of writing, Firefox only supports :has() behind an experimental flag.
  • My solution doesn’t include all possible typographic elements. For instance, there’s no <blockquote> in my demo. The selector list is easy enough to extend though.
  • My solution also doesn’t handle non-typographic elements that may be present in your particular long-form text blocks, e.g. <img>. That’s because for the sites I work on, we tend to lock down the WYSIWYG as much as possible to core text nodes, like headings, paragraphs, and lists. Anything else — e.g. quotes, images, tables, etc. — is a separate CMS component block, and those blocks themselves are spaced apart from each other when rendered on a page. But again, the selector list can be extended.
  • I’ve only included h1 for the sake of completeness. I usually wouldn’t allow a CMS user to add an h1 via WYSIWYG, as the page title would be baked into the page template somewhere rather than entered in the CMS page editor.
  • I’m not catering for a heading followed immediately by the same level heading (h2 + h2). This would mean that the first heading wouldn’t “own” any content, which seems like a misuse of headings (and, correct me if I’m wrong, but it might violate WCAG 1.3.1 Info and Relationships). I’m also not catering for skipped heading levels, which are invalid.
  • I am in no way knocking the existing approaches I mentioned. If and when I build another Tailwind site I’ll use the excellent Typography plugin, no question!
  • I’m not a designer. I came up with these spacing values by eyeballing it. You probably could (and should) use better values.

Specificity and project structure

I was going to write a whole big thing here about how the traditional method and the new :has() way of doing it might fit into the ITCSS methodology… But now that we have :where() (the zero-specificity selector) you can pretty much choose your preferred level of specificity for any selector now.

That said, the fact that we’re no longer dealing with a wrapper — .prose, .rich-text, etc. — to me makes it feel like this should live in the “elements” layer, i.e. before you start dealing with class-level specificity. I’ve used :where() in my examples to keep specificity consistent. All the selectors in both of my examples have a specificity score of 0,0,1 (except for the bare-bones reset).

Wrapping up

So there you have it, a bleeding-edge solution to a very boring problem! This newer approach is still not what I’d call “simple” CSS — as I said at the beginning, it’s a more complex topic than it might seem at first. But aside from having a few slightly complex selectors, I think the new approach makes more sense overall, and the less rigid HTML structure seems very appealing.

If you end up using this, or something like it, I’d love to know how it works out for you. And if you can think of ways to improve it, I’d love to hear those too!

Solved With :has(): Vertical Spacing in Long-Form Text originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.


, , , ,

When is it OK to Disable Text Selection?

Using CSS, it’s possible to prevent users from selecting text within an element using user-select: none. Now, it’s understandable why doing so might be considered “controversial”. I mean, should we be disabling standard user behaviors? Generally speaking, no, we shouldn’t be doing that. But does disabling text selection have some legitimate (albeit rare) use-cases? I think so.

In this article we’ll explore these use cases and take a look at how we can use user-select: none to improve (not hinder) user experiences. It’s also worth nothing that the user-select property has other values besides none that can be used to alter the behavior of text selection rather than disable it completely, and another value that even enforces text selection, so we’ll also take a look at those.

Possible user-select values

Let’s kick things off by running through the different user-select values and what they do.

Applying user-select: none; to an element means that its text content and nested text content won’t be functionally selectable or visually selectable (i.e. ::selection won’t work). If you were to make a selection that contained some non-selectable content, the non-selectable content would be omitted from the selection, so it’s fairly well implemented. And the support is great.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.


Chrome Firefox IE Edge Safari
4* 2* 10* 12* 3.1*

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
105 104 2.1* 3.2*

Adversely, user-select: text makes the content selectable. You’d use this value to overwrite user-select: none.

user-select: contain is an interesting one. Applying it means that if a selection begins within the element then it must end within it too, containing it. This oddly doesn’t apply when the selection begins before the element, however, which is probably why no browser currently supports it. (Internet Explorer and earlier versions of Microsoft Edge previously supported it under the guise of user-select: element.)

With user-select: all, selecting part of the element’s content results in all of it being selected automatically. It’s all or nothing, which is very uncompromising but useful in circumstances where users are more likely to copy content to their clipboard (e.g. sharing and embedding links, code snippets, etc.). Instead of double-clicking, users will only need to click once for the content to auto-select.

Be careful, though, since this isn’t always the feature you think it is. What if users only want to select part of the content (e.g. only the font name part of a Google Fonts snippet or one part of a code snippet)? It’s still better to handle ”copy to clipboard” using JavaScript in many scenarios.

A better application of user-select: all is to ensure that quotes are copied entirely and accurately.

The behavior of user-select: auto (the initial value of user-select) depends on the element and how it’s used. You can find out more about this in our almanac.

Now let’s turn to exploring use cases for user-select: none

Stripping non-text from the selection

When you’re copying content from a web page, it’s probably from an article or some other type of long-form content, right? You probably don’t want your selection to include images, emoji (which can sometimes copy as text, e.g. “:thinkingface:”), and other things that you might expect to find wrapped in an <aside> element (e.g. in-article calls to action, ads, or something else that’s not part of the main content).

To prevent something from being included in selections, make sure that it’s wrapped in an HTML element and then apply user-select: none to it:

<p>lorem <span style="user-select: none">🤔</span> ipsum</p>  <aside style="user-select: none">   <h1>Heading</h1>   <p>Paragraph</p>   <a>Call to action</a> </aside>

In scenarios like this, we’re not disabling selection, but rather optimizing it. It’s also worth mentioning that selecting doesn’t necessarily mean copying — many readers (including myself) like to select content as they read it so that they can remember where they are (like a bookmark), another reason to optimize rather than disable completely.

Preventing accidental selection

Apply user-select: none to links that look like buttons (e.g. <a href="/whatever" class="button">Click Me!</a>).

It’s not possible to select the text content of a <button> or <input type="submit"> because, well, why would you? However, this behavior doesn’t apply to links because traditionally they form part of a paragraph that should be selectable.

Fair enough.

We could argue that making links look like buttons is an anti-pattern, but whatever. It’s not breaking the internet, is it? That ship has sailed anyway, so if you’re using links designed to look like buttons then they should mimic the behavior of buttons, not just for consistency but to prevent users from accidentally selecting the content instead of triggering the interaction.

I’m certainly prone to selecting things accidentally since I use my laptop in bed more than I care to admit. Plus, there are several medical conditions that can affect control and coordination, turning an intended click into an unintended drag/selection, so there are accessibility concerns that can be addressed with user-select too.

Interactions that require dragging (intentionally) do exist too of course (e.g. in browser games), but these are uncommon. Still, it just shows that user-select does in fact have quite a few use-cases.

Avoiding paywalled content theft

Paywalled content gets a lot of hate, but if you feel that you need to protect your content, it’s your content — nobody has the right steal it just because they don’t believe they should pay for it.

If you do want to go down this route, there are many ways to make it more difficult for users to bypass paywalls (or similarly, copy copyrighted content such as the published work of others).

Blurring the content with CSS:

article { filter: blur(<radius>); }

Disabling the keyboard shortcuts for DevTools:

document.addEventListener("keydown", function (e) {   if (e.keyCode == 123) e.preventDefault();   else if ((e.ctrlKey || e.metaKey) && e.altKey && e.keyCode == 73) e.preventDefault();   else if ((e.ctrlKey || e.metaKey) && e.altKey && e.keyCode == 74) e.preventDefault();   else if ((e.ctrlKey || e.metaKey) && e.altKey && e.keyCode == 85) e.preventDefault(); });

Disabling access to DevTools via the context menu by disabling the context menu itself:

document.addEventListener("contextmenu", e => e.preventDefault())

And of course, to prevent users from copying the content when they’re not allowed to read it at the source, applying user-select: none:

<article style="user-select: none">

Any other use cases?

Those are the three use cases I could think of for preventing text selection. Several others crossed my mind, but they all seemed like a stretch. But what about you? Have you had to disable text selection on anything? I’d like to know!

When is it OK to Disable Text Selection? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.


, ,

Recreating MDN’s Truncated Text Effect

It’s no secret that MDN rolled out a new design back in March. It’s gorgeous! And there are some sweet CSS-y gems in it that are fun to look at. One of those gems is how card components handle truncated text.

Pretty cool, yeah? I wanna tear that apart in just a bit, but a couple of things really draw me into this approach:

  • It’s an example of intentionally cutting off content. We’ve referred to that as CSS data loss in other places. And while data loss is generally a bad thing, I like how it’s being used here since excerpts are meant to be a teaser for the full content.
  • This is different than truncating text with text-overflow: ellipsis, a topic that came up rather recently when Eric Eggert shared his concerns with it. The main argument against it is that there is no way to recover the text that gets cut off in the truncation — assistive tech will announce it, but sighted users have no way to recover it. MDNs approach provides a bit more control in that department since the truncation is merely visual.

So, how did MDN do it? Nothing too fancy here as far the HTML goes, just a container with a paragraph.

<div class="card">   <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Inventore consectetur temporibus quae aliquam nobis nam accusantium, minima quam iste magnam autem neque laborum nulla esse cupiditate modi impedit sapiente vero?</p> </div>

We can drop in a few baseline styles to shore things up.

Again, nothing too fancy. Our goal is cut the content off after, say, the third line. We can set a max-height on the paragraph and hide the overflow for that:

.card p {   max-height: calc(4rem * var(--base)); /* Set a cut-off point for the content */   overflow: hidden; /* Cut off the content */ }

Whoa whoa, what’s up with that calc() stuff? Notice that I set up a --base variable up front that can be used as a common multiplier. I’m using it to compute the font-size, line-height, padding for the card, and now the max-height of the paragraph. I find it easier to work with a constant values especially when the sizing I need is really based on scale like this. I noticed MDN uses a similar --base-line-height variable, probably for the same purpose.

Getting that third line of text to fade out? It’s a classic linear-gradient() on the pargraph’s :after pseudo-element, which is pinned to the bottom-right corner of the card. So, we can set that up:

.card p:after {   content: ""; /* Needed to render the pseudo */   background-image: linear-gradient(to right, transparent, var(--background) 80%);   position: absolute;   inset-inline-end: 0; /* Logical property equivalent to `right: 0` */ }

Notice I’m calling a --background variable that’s set to the same background color value that’s used on the .card itself. That way, the text appears to fade into the background. And I found that I needed to tweak the second color stop in the gradient because the text isn’t completely hidden when the gradient blends all the way to 100%. I found 80% to be a sweet spot for my eyes.

And, yes, :after needs a height and width. The height is where that --base variables comes back into play because we want that scaled to the paragraph’s line-height in order to cover the text with the height of :after.

.card p:after {   /* same as before */   height: calc(1rem * var(--base) + 1px);   width: 100%; /* relative to the .card container */ }

Adding one extra pixel of height seemed to do the trick, but MDN was able to pull it off without it when I peeked at DevTools. Then again, I’m not using top (or inset-block-start) to offset the gradient in that direction either. 🤷‍♂️

Now that p:after is absolutely positioned, we need to explicitly declare relative positioning on the paragraph to keep :after in its flow. Otherwise, :after would be completely yanked from the document flow and wind up outside of the card. This becomes the full CSS for the .card paragraph:

.card p {   max-height: calc(4rem * var(--base)); /* Set a cut-off point for the content */   overflow: hidden; /* Cut off the content */   position: relative; /* needed for :after */ }

We’re done, right? Nope! The dang gradient just doesn’t seem to be in the right position.

I’ll admit I brain-farted on this one and fired up DevTools on MDN to see what the heck I was missing. Oh yeah, :after needs to be displayed as a block element. It’s clear as day when adding a red border to it.🤦‍♂️

.card p:after {   content: "";   background: linear-gradient(to right, transparent, var(--background) 80%);   display: block;   height: calc(1rem * var(--base) + 1px);   inset-block-end: 0;   position: absolute;   width: 100%; }

All together now!

And, yep, looks sounds like VoiceOver respects the full text. I haven’t tested any other screen readers though.

I also noticed that MDN’s implementation removes pointer-events from p:after. Probably a good defensive tactic to prevent odd behaviors when selecting text. I added it in and selecting text does feel a little smoother, at least in Safari, Firefox, and Chrome.

Recreating MDN’s Truncated Text Effect originally published on CSS-Tricks. You should get the newsletter.


, , , ,

Just How Long Should Alt Text Be?

I teach a class over at the local college here in Long Beach and a majority of the content is hosted on the Canvas LMS so students can access it online. And, naturally, I want the content to be as accessible as possible, so thank goodness Canvas has a11y tooling built right into it.

But it ain’t all that rosy. It makes assumptions like all other a11y tooling and adheres to guidelines that were programmed into it. It’s not like the WCAG is baked right in and updated when it updates.

The reason this is even on my mind is that Jeremy yesterday described his love for writing image descriptions:

I enjoy writing alt text. I recently described how I updated my posting interface here on my own site to put a textarea for alt text front and centre for my notes with photos. Since then I’ve been enjoying the creative challenge of writing useful—but also evocative—alt text.

I buy into that! Writing alt text is a challenge that requires a delicate dance between the technical and the creative. It’s both an opportunity to make content more accessible and enhance the user experience.

One of those programmed guidelines in the Canvas tool is a cap of 120 characters on alt text. Why 120? I dunno, I couldn’t find any supporting guideline or rule for that exact number. One answer is that screen readers stop announcing text after 125 characters, but that’s apparently untrue, at least today. The general advice for how long alt text should be comes in varying degrees:

  • Jake Archibald talks of length in terms of emotion. Detail is great, but too much detail might distort the focal point, which makes total sense.
  • Dave sees them as short, succinct paragraphs.
  • Carrie Fisher suggests a 150-character limit not because screen readers will truncate them but more as a mental note that maybe things are getting too descriptive.
  • Daniel Göransson says in this 2017 guide that it comes down to context and knowing when certain details of an image are worth additional explanation. But he generally errs on the side of conciseness.

So, how long should alt text be? The general consensus here is that there is no hard limit, but more of a contextual awareness of what purpose the image serves and adapting to it accordingly.

Which gets me back to Jeremy’s article. He was writing alt text for a group of speaker headshots and realized the text was all starting to sound the same. He paused, thought about the experience, compared it to the experience of a sighted user, and created parity between them:

The more speakers were added to the line-up, the more I felt like I was repeating myself with the alt text. […] The experience of a sighted person looking at a page full of speakers is that after a while the images kind of blend together. So if the alt text also starts to sound a bit repetitive after a while, maybe that’s not such a bad thing. A screen reader user would be getting an equivalent experience.

I dig that. So if you’re looking for a hard and fast rule on character counts, sorry to disappoint. Like so many other things, context is king and that’s the sort of thing that can’t be codified, or even automated for that matter.

And while we’re on the topic, just noticed that Twitter has UI to display alt text:

Now if only there was more contrast between that text and the background… a11y is hard.

Just How Long Should Alt Text Be? originally published on CSS-Tricks. You should get the newsletter.


, , ,

Cool Hover Effects That Use CSS Text Shadow

In my last article we saw how CSS background properties allow us to create cool hover effects. This time, we will focus on the CSS text-shadow property to explore even more interesting hovers. You are probably wondering how adding shadow to text can possibly give us a cool effect, but here’s the catch: we’re not actually going to make any shadows for these text hover effects.

text-shadow but no text shadows?

Let me clear the confusion by showing the hover effects we are going to build in the following demo:

Without looking at the code many of you will, intuitively, think that for each hover effect we are duplicating the text and then independently animating them. Now, if you check the code you will see that none of the text is actually duplicated in the HTML. And did you notice that there is no use of content: "text" in the CSS?

The text layers are completely made with text-shadow!

Hover effect #1

Let’s pick apart the CSS:

.hover-1 {   line-height: 1.2em;   color: #0000;   text-shadow:      0 0 #000,      0 1.2em #1095c1;   overflow: hidden;   transition: .3s; } .hover-1:hover {   text-shadow:      0 -1.2em #000,      0 0 #1095c1; }

The first thing to notice is that I am making the color of the actual text transparent (using #0000) in order to hide it. After that, I am using text-shadow to create two shadows where I am defining only two length values for each one. That means there’s no blur radius, making for a sharp, crisp shadow that effectively produces a copy of the text with the specified color.

That’s why I was able to claim in the introduction that there are no shadows in here. What we’re doing is less of a “classic” shadow than it is a simple way to duplicate the text.

Diagram of the start and end of the hover effect.

We have two text layers that we move on hover. If we hide the overflow, then the duplicated text is out of view and the movement makes it appear as though the actual text is being replaced by other text. This is the main trick that that makes all of the examples in this article work.

Let’s optimize our code. I am using the value 1.2em a lot to define the height and the offset of the shadows, making it an ideal candidate for a CSS custom property (which we’re calling --h):

.hover-1 {   --h: 1.2em;    line-height: var(--h);   color: #0000;   text-shadow:      0 0 #000,      0 var(--h) #1095c1;   overflow: hidden;   transition: .3s; } .hover-1:hover {   text-shadow:      0 calc(-1 * var(--h)) #000,      0 0 #1095c1; }

We can still go further and apply more calc()-ulations to streamline things to where we only use the text-shadow once. (We did the same in the previous article.)

.hover-1 {   --h: 1.2em;       line-height: var(--h);   color: #0000;   text-shadow:      0 calc(-1*var(--_t, 0em)) #000,      0 calc(var(--h) - var(--_t, 0em)) #1095c1;   overflow: hidden;   transition: .3s; } .hover-1:hover {   --_t: var(--h); }

In case you are wondering why I am adding an underscore to the --_t variable, it’s just a naming convention I am using to distinguish between the variables we use to control the effect that the user can update (like --h) and the internal variables that are only used for optimization purposes that we don’t need to change (like --_t ). In other words, the underscore is part of the variable name and has no special meaning.

We can also update the code to get the opposite effect where the duplicated text slides in from the top instead:

All we did is a small update to the text-shadow property — we didn’t touch anything else!

Hover effect #2

For this one, we will animate two properties: text-shadow and background. Concerning the text-shadow, we still have two layers like the previous example, but this time we will move only one of them while making the color of the other one transparent during the swap.

.hover-2 {   /* the height */   --h: 1.2em;    line-height: var(--h);   color: #0000;   text-shadow:      0 var(--_t, var(--h)) #fff,     0 0 var(--_c, #000);   transition: 0.3s; } .hover-2:hover {   --_t: 0;   --_c: #0000; }

On hover, we move the white text layer to the top while changing the color of the other one to transparent. To this, we add a background-size animation applied to a gradient:

And finally, we add overflow: hidden to keep the animation only visible inside the element’s boundaries:

.hover-2 {   /* the height */   --h: 1.2em;    line-height: var(--h);   color: #0000;   text-shadow:      0 var(--_t,var(--h)) #fff,     0 0 var(--_c, #000);   background:      linear-gradient(#1095c1 0 0)      bottom/100% var(--_d, 0) no-repeat;   overflow: hidden;   transition: 0.3s; } .hover-2:hover {   --_d: 100%;   --_t: 0;   --_c: #0000; }

What we’ve done here is combine the CSS text-shadow and background properties to create a cool hover effect. Plus, we were able to use CSS variables to optimize the code.

If the background syntax looks strange to you, I highly recommend reading my previous article. The next hover effect also relies on an animation I detailed in that article. Unless you are comfortable with CSS background trickery, I’d suggest reading that article before continuing this one for more context.

In the previous article, you show us how to use only one variable to create the hover effect — is it possible to do that here?

Yes, absolutely! We can indeed use that same DRY switching technique so that we’re only working with a single CSS custom property that merely switches values on hover:

.hover-2 {   /* the height */   --h: 1.2em;    line-height: var(--h);   color: #0000;   text-shadow:      0 var(--_i, var(--h)) #fff,     0 0 rgb(0 0 0 / calc(var(--_i, 1) * 100%) );   background:      linear-gradient(#1095c1 0 0)      bottom/100% calc(100% - var(--_i, 1) * 100%) no-repeat;   overflow: hidden;   transition: 0.3s; } .hover-2:hover {   --_i: 0; }

Hover effect #3

This hover effect is nothing but a combination of two effects we’ve already made: the second hover effect of the previous article and the first hover effect in this article.

.hover-3 {   /* the color  */   --c: #1095c1;   /* the height */   --h: 1.2em;    /* The first hover effect in this article */   line-height: var(--h);     color: #0000;   overflow: hidden;   text-shadow:      0 calc(-1 * var(--_t, 0em)) var(--c),      0 calc(var(--h) - var(--_t, 0em)) #fff;   /* The second hover effect from the previous article */   background:      linear-gradient(var(--c) 0 0) no-repeat      calc(200% - var(--_p, 0%)) 100% / 200% var(--_p, .08em);   transition: .3s var(--_s, 0s), background-position .3s calc(.3s - var(--_s, 0s)); } .hover-3:hover {   --_t: var(--h);   --_p: 100%;   --_s: .3s }

All I did was copy and paste the effects from those other examples and make minor adjustments to the variable names. They make for a neat hover effect when they’re combined! At first glance, such an effect may look complex and difficult but, in the end, it’s merely two relatively easy effects made into one.

Optimizing the code with the DRY switching variable technique should also be an easy task if we consider the previous optimizations we’ve already done:

.hover-3 {   /* the color  */   --c: #1095c1;   /* the height */   --h: 1.2em;    line-height: var(--h);     color: #0000;   overflow: hidden;   text-shadow:      0 calc(-1 * var(--h) * var(--_i, 0)) var(--c),      0 calc(var(--h) * (1 - var(--_i, 0))) #fff;   background:      linear-gradient(var(--c) 0 0) no-repeat     calc(200% - var(--_i, 0) * 100%) 100% / 200% calc(100% * var(--_i, 0) + .08em);   transition: .3s calc(var(--_i, 0) * .3s), background-position .3s calc(.3s - calc(var(--_i, 0) * .3s)); } .hover-3:hover {   --_i: 1; }

Hover effect #4

This hover effect is an improvement of the second one. First, let’s introduce a clip-path animation to reveal one of the text layers before it moves:

Here’s another illustration to better understand what is happening:

Diagram of the start and end of the text hover.

Initially, we use inset(0 0 0 0) which is similar to overflow: hidden in that all we see is the actual text. On hover, we update the the third value (which represent the bottom offset) using a negative value equal to the height to reveal the text layer placed at the bottom.

From there, we can add this to the second hover effect we made in this article, and this is what we get:

We are getting closer! Note that we need to first run the clip-path animation and then everything else. For this reason, we can add a delay to all of the properties on hover, except clip-path:

transition: 0.4s 0.4s, clip-path 0.4s;

And on mouse out, we do the opposite:

transition: 0.4s, clip-path 0.4s 0.4s;

The final touch is to add a box-shadow to create the sliding effect of the blue rectangle. Unfortunately, background is unable to produce the effect since backgrounds are clipped to the content area by default. Meanwhile, box-shadow can go outside the content area.

.hover-4 {   /* the color  */   --c: #1095c1;   /* the height */   --h: 1.2em;      line-height: var(--h);   color: #0000;   text-shadow:      0 var(--_t, var(--h)) #fff,     0 0 var(--_c, #000);   box-shadow: 0 var(--_t, var(--h)) var(--c);   clip-path: inset(0 0 0 0);   background: linear-gradient(var(--c) 0 0) 0 var(--_t, var(--h)) no-repeat;   transition: 0.4s, clip-path 0.4s 0.4s; } .hover-4:hover {   --_t: 0;   --_c: #0000;   clip-path: inset(0 0 calc(-1 * var(--h)) 0);   transition: 0.4s 0.4s, clip-path 0.4s; }

If you look closely at the box-shadow, you will see it has the same values as the white text layer inside text-shadow. This is logical since both need to move the same way. Both will slide to the top. Then the box-shadow goes behind the element while text-shadow winds up on the top.

Here is a demo with some modified values to visualize how the layers move:

Wait, The background syntax is a bit different from the one used in the second hover effect!

Good catch! Yes, we are using a different technique with background that produces the same effect. Instead of animating the size from 0% to 100%, we are animating the position.

If we don’t specify a size on our gradient, then it take up the full width and height by default. Since we know the height of our element (--h) we can create a sliding effect by updating the position from 0 var(--h) to 0 0.

.hover-4 {   /* ... */   background: linear-gradient(var(--c) 0 0) 0 var(--_t, var(--h)) no-repeat; } .hover-4:hover {   --_t: 0; }

We could have used the background-size animation to get the same effect, but we just added another trick to our list!

In the demos, you also used inset(0 0 1px 0)… why?

I sometimes add or remove a few pixels or percentages here and there to refine anything that looks off. In this case, a bad line was appearing at the bottom and adding 1px removed it.

What about the DRY switch variable optimization?

I am leaving this task for you! After those four hover effects and the previous article, you should be able to update the code so it only uses one variable. I’d love to see you attempt it in the comments!

Your turn!

Let me share one last hover effect which is another version of the previous one. Can you find out how it’s done without looking at the code? It’s a good exercise, so don’t cheat!

Wrapping up

We looked at a bunch of examples that show how one element and few lines of CSS are enough to create some pretty complex-looking hover effects on text elements — no pseudo elements needed! We were even able to combine techniques to achieve even more complex animations with a small amount of effort.

If you’re interested in going deeper than the four text-shadow hover effects in this article, check my collection of 500 hover effects where I am exploring all kinds of different techniques.

Cool Hover Effects That Use CSS Text Shadow originally published on CSS-Tricks. You should get the newsletter.


, , , ,

CSS Custom Highlight API: The Future of Highlighting Text Ranges on the Web

Styling ranges of text in software is a very useful thing to be able to do. Thankfully, we have the CSS Custom Highlight API to look forward to because it represents the future of styling text ranges on the web.

Animation screenshot of the CSS Custom Highlight API demo.

One example: if you’ve ever used text editing software like Google Docs, Word, or Dropbox Paper, you’ll see they detect spelling and grammar errors and displaying nice little squiggly underlines below them to attract attention. Code editors like VS Code do the same for code errors.

Another very common use case for highlighting text is search and highlight, where you’re given a text input box and typing in it searches matching results on the page, and highlights them. Try pressing Ctrl/+ F in your web browser right now and type in some text from this article.

The browser itself often handles these styling situations. Editable areas (like a <textarea>) get spelling squiggles automatically. The find command highlights found text automatically.

But what about when we want to do this type of styling ourselves? Doing this on the web has been a common problem for a long time. It has probably costed many people a lot more time than it should have.

This isn’t a simple problem to solve. We aren’t just wrapping text in a <span> with a class and applying some CSS. Indeed, this requires being able to correctly highlight multiple ranges of text across an arbitrarily complex DOM tree, and possibly crossing the boundaries of DOM elements.

There are two common solutions to this, including:

  1. styling text range pseudo-elements, and
  2. creating your own text highlighting system.

We’ll review them first and then take a look at the upcoming CSS Custom Highlight API that can change it all. but if you’re

Potential Solution #1: Style-able Text Ranges

Probably the most well-known style-able text range is the user selection. When you use your pointing device to select a piece of text in a web page, a Selection object is automatically created. In fact, try selecting text on this page right now, and then run document.getSelection() in the DevTools console. You should see location information about the selected text.

DevTools window showing the position of the current selection in the console.

It turns out that you can also create a text selection programmatically from JavaScript. Here is an example:

// First, create a Range object. const range = new Range();  // And set its start and end positions. range.setStart(parentNode, startOffset); range.setEnd(parentNode, endOffset);  // Then, set the current selection to this range. document.getSelection().removeAllRanges(); document.getSelection().addRange(range);

The last piece of the puzzle is to style this range. CSS has a pseudo-element called ::selection to do just that, and it’s supported across all browsers.

::selection {   background-color: #f06;   color: white; }

Here is an example using this technique to highlight all words in a page one after the other:

On top of the ::selection pseudo-element, there are a number of other pseudo-elements:

  • ::target-text selects the text that has been scrolled to in browsers that support the scroll-to-text feature. (MDN)
  • ::spelling-error selects text that is flagged by the browser as containing a spelling error. (MDN)
  • ::grammar-error selects text that is flagged by the browser as containing a grammar error. (MDN)

Unfortunately browser support isn’t great here and although these ranges are useful in each of their own right, they can’t be used to style custom pieces of text — only browser-predefined ones

So the user text selection is nice because it’s relatively simple to put in place and doesn’t change the DOM of the page. Indeed, Range objects are essentially coordinates of segments in the page, rather than HTML elements that need to be created to exist.

One major drawback, however, is that creating a selection resets whatever the user has already manually selected. Try selecting text in the demo above to test this. You’ll see how it goes away as soon as the code moves the selection somewhere else.

Potential Solution #2: Custom Highlighting System

This second solution is pretty much the only thing you can do if using the Selection object is insufficient for you. This solution revolves around doing everything yourself, using JavaScript to insert new HTML elements in the DOM where you want the highlighting to appear.

Unfortunately, this means way more JavaScript to write and maintain, not to mention it forces the browser to re-create the layout of the page whenever the highlighting changes. Plus, there are complicated edge cases, for example, when you want to highlight a piece of text that spans across multiple DOM elements.

Illustration showing a line of HTML with an emphasis element and a strong element with a bright yellow highlight running through them.

Interestingly, CodeMirror and Monaco (the JavaScript text editor library that powers VS Code) have their own highlighting logic. They use a slightly different approach where the highlights are contained in a separate part of the DOM tree. The lines of text and the highlighted segments are rendered in two different places in the DOM which are then positioned over each other. If you inspect the DOM sub-tree that contains the text, there are no highlights. This way, the highlights can be re-rendered without impacting the lines of text and having to introduce new elements within them.

Overall, it feels like a browser-powered highlighting feature is missing. Something that would help solve all of these drawbacks (no interference with user text selection, multi-selection support, simple code) and be faster than custom-made solutions.

Fortunately, that’s what we’re here to talk about!

Enter the CSS Custom Highlight API

The CSS Custom Highlight API is a new W3C specification (currently in Working Draft status) that makes it possible to style arbitrary text ranges from JavaScript! The approach here is very similar to the user text selection technique we reviewed earlier. It gives developers a way to create arbitrary ranges, from JavaScript, and then style them using CSS.

Creating Ranges of Text

The first step is to create the ranges of text that you want to highlight. which can be done using a Range in JavaScript. So, like we did when setting the current selection:

const range = new Range(); range.setStart(parentNode, startOffset); range.setEnd(parentNode, endOffset);

It’s worth noting that the setStart and setEnd methods work differently if the node passed as the first argument is a text node or not. For text nodes, the offset corresponds to the number of characters within the node. For other nodes, the offset corresponds to the number of child nodes within the parent node.

Also worth noting is that setStart and setEnd aren’t the only ways to describe where a range starts and ends. Take a look at the other methods available on the Range class to see other options.

Creating Highlights

The second step consists in creating Highlight objects for the ranges created in that last step. A Highlight object can receive one or more Ranges. So if you want to highlight a bunch of pieces of text in exactly the same way, you should probably create a single Highlight object and initialize it with all of the Ranges that correspond to these pieces of text.

const highlight = new Highlight(range1, range2, ..., rangeN);

But you can also create as many Highlight objects as you need. For example, if you are building a collaborative text editor where each user gets a different text color, then you can create one Highlight object per user. Each object can then be styled differently, as we’ll see next.

Registering Highlights

Now Highlight objects on their own don’t do anything. They first need to be registered in what is called the highlight registry. This is done by using the CSS Highlights API. The registry works like a map where you can register new highlights by giving them names, as well as remove highlights (or even clear the entire registry).

Here is how to register a single highlight.

CSS.highlights.set('my-custom-highlight', highlight);

Where my-custom-highlight is the name of your choosing and highlight is a Highlight object created in the last step.

Styling Highlights

The final step is to actually style the registered highlights. This is done with the new CSS ::highlight() pseudo-element, using the name you chose when registering the Highlight object (which is my-custom-highlight in our example above).

::highlight(my-custom-highlight) {   background-color: yellow;   color: black; }

It’s worth noting that, just like ::selection, a subset of CSS properties only can be used with the ::highlight() pseudo-element:

Updating Highlights

There are multiple ways to update highlighted text on the page.

For example, you can clear the highlight registry altogether with CSS.highlights.clear() and then start again from the beginning. Or, you can also update the underlying ranges without having to re-create any of the objects all. For this, use the range.setStart and range.setEnd methods again (or any of the other Range methods) and the highlights will be re-painted by the browser.

But, the Highlight object works like a JavaScript Set, so this means you also add new Range objects to an existing Highlight with highlight.add(newRange) or remove a Range with highlight.delete(existingRange).

Third, you can also add or remove specific Highlight objects from the CSS.highlights registry. Since this API works like a JavaScript Map, you can set and delete to update the currently registered Highlights.

Browser Support

The specification for the CSS Custom Highlight API is relatively new and its implementation in browsers is still incomplete. So, although this is going to be a very useful addition to the web platform, it’s not quite ready for production use.

The Microsoft Edge team is implementing the CSS Custom Highlight API in Chromium at the moment. In fact, the feature can already be used in Canary versions right now by enabling the Experimental Web Platform features flag (under about:flags). There is currently no firm plan as to when the feature will ship in Chrome, Edge, and other Chromium-based browsers, but it’s getting very close.

The API is also supported in Safari 99+ but behind an experiment flag (Develop → Experimental Features → Highlight API), and the interface is a little bit different in that it uses StaticRange objects instead.

Firefox does not support the API yet, though you can read Mozilla’s position about it for more information.


Speaking of Microsoft Edge, they have a demo set up where you can take the CSS Custom Highlight API for a test drive. But Before trying the demo, be sure you’re using either Chrome or Edge Canary with the Experimental Web Platform features flag in the about:flags page.

/button View the demo

The demo uses the Custom Highlight API to highlight ranges of text in the page based on what you type in the search field at the top of the page.

After the page loads, JavaScript code retrieves all the text nodes in the page (using a TreeWalker) and when the user types in the search field, the code iterates over these nodes until it finds matches. Those matches are then used to create Range objects, which are then highlighted with the Custom Highlight API.

Closing Thoughts

So, is this new browser-provided highlighting API really worth it? Totally!

For one, even if the CSS Custom Highlight API may seem a bit complicated at first (i.e. having to create ranges, then highlights, then registering them, and finally styling them), it’s still way simpler than having to create new DOM elements and insert them in the right places.

More importantly, browser engines can style these ranges very, very fast.

The reason only a subset of CSS properties is allowed to be used with the ::highlight() pseudo-element is that the subset only contains properties that can be applied by the browser very effectively without having to recreate the layout of the page. Highlighting ranges of text by inserting new DOM elements in the page around them requires the engine to do much more work.

But don’t take my word for it. Fernando Fiori, who worked on the API, created this nice performance comparison demo. On my computer, the CSS Custom Highlight API performs on average 5✕ as fast as the DOM-based highlighting.

With Chromium and Safari experimental support already here, we’re getting close to something that can be used in production. I can’t wait for browsers to support the Custom Highlight API consistently and see what features this will unlock!

CSS Custom Highlight API: The Future of Highlighting Text Ranges on the Web originally published on CSS-Tricks. You should get the newsletter.


, , , , ,

Care for the Text

How do you make a great website? Everyone has an answer at the ready: Flashy animations! The latest punk-rock CSS trick! Gradients! Illustrations! Colors to pack a punch! Vite! And, sure, all these things might make a website better. But no matter how fancy the application is or how dazzling the technology will ever be under the hood, a great website will always require great text.

So, whenever I’m stuck pondering the question: “how do I make this website better?” I know the answer is always this:

care for the text.

Without great writing, a website is harder to read, extremely difficult to navigate, and impossible to remember. Without great writing, it’s hardly a website at all. But it’s tough to remember this day in and day out—especially when it’s not our job to care about the text—yet each and every <p> tag and <button> element is an opportunity for great writing. It’s a moment to inject some humor or add a considerate note that helps people.

So: care for the text. Got it. But there are so many ways to care! From commas and smart quotes, to labels in our forms, to typography, and even the placeholders in our inputs. It’s a dizzying amount of responsibility—but it’s worth every second of our time.

Here’s one example: a while ago, we needed to explain a new feature to our users and point to it in the UI. We could use our pop-up component to explain how our team just fixed something for a ton of folks—but!—I knew that no matter what the fancy new feature was, our customers would be annoyed by a pop-up.

After thinking about it for far too long I realized that this was an opportunity to acknowledge how annoying this popup was:

With this project, I could’ve just thrown some text in that button that says “Dismiss” but our little team of writers at Sentry constantly remind me that even the smallest, most boring block of text can be a playground. Each string has potential, even in this dumb example. It doesn’t change the world or anything, but it improves something that would otherwise be yawn-worthy, predictable.

Not every bit of text in a website needs to be passive-aggressive though. When you’re in the checkout ordering medicine, you likely don’t want to be reading a quirky story or a poem, and you don’t want to click a button that insults you. In this context, caring for the text means something entirely different. It’s about not getting in the way but being as efficient and empathetic as possible. And this is true of every link in the footer, every navigation item, every <alt> tag, and subtitle—they all require care and attention. Because all of these details add up.

These are the details that make a good website great.



Detecting Specific Text Input with HTML and CSS

Louis Lazaris breaks down some bonafide CSS trickery from Jane. The Pen shows off interactivity where:

  1. You have to press a special combination of keys on a keyboard.
  2. Then type a secret password.

From there, a special message pops up on the screen. Easily JavaScript territory, but no, this is done here entirely in HTML and CSS, which is wild.

A lot of little known features and tricks is combined here to pull this off, like HTML’s accesskey and pattern attributes, as well as :not(), :placeholder-shown, and :valid in CSS—not to mention the custom property toggle trick.

That’s… wow. And yet, look how very little code it is.

To Shared LinkPermalink on CSS-Tricks

The post Detecting Specific Text Input with HTML and CSS appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.


, , , ,

How to Create Neon Text With CSS

Neon text can add a nice, futuristic touch to any website. I’ve always loved the magic of neon signs, and wanted to recreate them using CSS. I thought I’d share some tips on how to do it! In this article, we’re going to take a look at how to add glowing effects to text. We’ll also take a look at various ways to animate the neon signs, all using CSS and keyframes.

Here’s what we’ll be making:

Adding a glow effect to text

First, let’s make the text glow. This can be done in CSS with the text-shadow property. What’s neat about text-shadow is that we can apply multiple shadows on it just by comma-separating them:

.neonText {   color: #fff;   text-shadow:     0 0 7px #fff,     0 0 10px #fff,     0 0 21px #fff,     0 0 42px #0fa,     0 0 82px #0fa,     0 0 92px #0fa,     0 0 102px #0fa,     0 0 151px #0fa; }

text-shadow requires four values, the first two of which represent the horizontal and vertical position of the shadow, respectively. The third value represents the size of the blur radius while the last value represents the color of the shadow. To increase the size of the glow effect, we would increase the third value, which represents the blur radius. Or, expressed another way:

text-shadow: [x-offset] [y-offset] [blur-radius] [color];

Here’s what we get with that small bit of CSS:

The next thing you might be wondering is what’s up with all of those values? How did I get those and why are there so many? First, we added white glow effects to the outer edges of the text’s letters with a small blur radius.

.neonText {   color: #fff;   text-shadow:     /* White glow */     0 0 7px #fff,     0 0 10px #fff,     0 0 21px #fff, }

The last five values are wider text shadows of a larger blur radius that forms the green glow.

.neonText {   color: #fff;   text-shadow:     /* White glow */     0 0 7px #fff,     0 0 10px #fff,     0 0 21px #fff,     /* Green glow */     0 0 42px #0fa,     0 0 82px #0fa,     0 0 92px #0fa,     0 0 102px #0fa,     0 0 151px #0fa; }

It’d be great if we could accomplish this with fewer than five shadows, but we need all these shadows so that they can be stacked over one another to add more depth to the glow. If we had used a single text-shadow instead, the effect would not have the depth required to make it look realistic.

Go ahead and experiment with various hues and colors as well as blur radius sizes! There’s a huge variety of cool glow effects you can make, so try different variations — you can even mix and match colors where one color blends into another.

The “flickering” effect

One thing you might notice about neon signs is that some of them — particularly older ones — tend to flicker. The light kind of goes in and out. We can do the same sort of thing with CSS animations! Let’s reach for @keyframes to make an animation that flickers the light on and off in quick, seemingly random flashes.

@keyframes flicker {   0%, 18%, 22%, 25%, 53%, 57%, 100% {     text-shadow:       0 0 4px #fff,       0 0 11px #fff,       0 0 19px #fff,       0 0 40px #0fa,       0 0 80px #0fa,       0 0 90px #0fa,       0 0 100px #0fa,       0 0 150px #0fa;   }   20%, 24%, 55% {            text-shadow: none;   } }

That’s really it! We’ve taken the exact same text-shadow property and values we had before, wrapped them in a @keyframes animation called flicker, and chose points in the timeline to apply the shadows, as well as points that completely remove the shadows.

All that’s left is to call the animation where we want the light to flicker. In this particular case, let’s only add it to the <h1> element. Having one part of the entire sign flicker feels a little more realistic than if we applied the flicker to all of the text.

h1 {   animation: flicker 1.5s infinite alternate;      } 

Note that if we did want the entire sign to flicker, then we could technically remove the text-shadow values on the .neonText class, add the animation to it, and let the @keyframes apply the shadows instead.

It’s quite a cool effect, and adds more realism to our neon text! Of course, there are other effects you could try out too, which will also be explored further in this article. For example, how about more of a pulsating animation or a more subtle flicker?

Let’s explore those and other effects!

Pulsating glow

We just got a quick peek at this. It uses keyframes, just as the previous example does, where we specify the size of the blur radius at the start and end of the animation.

We want the size of the blur radius to be smallest at the end of the animation, so we simply decrease the blur radius values for each text-shadow value in the 0% keyframe. This way, the size of the blur gradually ebbs and flows, creating a pulsating effect.

@keyframes pulsate {   100% {     /* Larger blur radius */     text-shadow:       0 0 4px #fff,       0 0 11px #fff,       0 0 19px #fff,       0 0 40px #0fa,       0 0 80px #0fa,       0 0 90px #0fa,       0 0 100px #0fa,       0 0 150px #0fa;   }   0% {     /* Smaller blur radius */     text-shadow:       0 0 2px #fff,       0 0 4px #fff,       0 0 6px #fff,       0 0 10px #0fa,       0 0 45px #0fa,       0 0 55px #0fa,       0 0 70px #0fa,       0 0 80px #0fa;   } }

Once again, we add the animation to some element. We’ll go with <h1> again:

h1 {   animation: pulsate 2.5s infinite alternate;      }

Here it is with it all put together:

Subtle flicker

We can tone things down a bit and make the flickering action super subtle. All we need to do is slightly decrease the size of the blur radius in the 0% keyframe, just not to the extent as seen in the previous example.

@keyframes pulsate {   100% {     /* Larger blur radius */     text-shadow:       0 0 4px #fff,       0 0 11px #fff,       0 0 19px #fff,       0 0 40px #f09,       0 0 80px #f09,       0 0 90px #f09,       0 0 100px #f09,       0 0 150px #f09;   }  0% {     /* A slightly smaller blur radius */     text-shadow:       0 0 4px #fff,       0 0 10px #fff,       0 0 18px #fff,       0 0 38px #f09,       0 0 73px #f09,       0 0 80px #f09,       0 0 94px #f09,       0 0 140px #f09;   } }

Since the flickering is more subtle and the reduction of the blur radius is not as large, we should increase the number of times this animation occurs per second in order to emulate more frequent flickering. This can be done by decreasing the animation’s duration, say to a mere 0.11s:

h1 {   animation: pulsate 0.11s ease-in-out infinite alternate;     }

Using a background image

It would be really neat if our sign was hanging on a wall instead of empty space. Let’s grab a background image for that, maybe some sort of brick texture from Unsplash or something:

body {   background-image: url(wall.jpg); }

Adding a border

One last detail we can add is some sort of circular or rectangular border around the sign. It’s just a nice way to frame the text and make it look like, you know, an actual sign. By adding a shadow to the border, we can give it the same neon effect as the text!

Whatever element is the container for the text is what needs a border. Let’s say we’re only working with an <h1> element. That’s what gets the border. We call the border shorthand property to make a solid white border around the heading, plus a little padding to give the text some room to breathe:

h1 {   border: 0.2rem solid #fff;   padding: 0.4em; }

We can round the corners of the border a bit so things aren’t so sharp by applying a border-radius on the heading. You can use whatever value works best for you to get the exact roundness you want.

h1 {   border: 0.2rem solid #fff;   border-radius: 2rem;   padding: 0.4em; }

The last piece is the glow! Now, text-shadow won’t work for the border here but that’s okay because that’s what the box-shadow property is designed to do. The syntax is extremely similar, so we can even pull exactly what we have for text-shadow and tweak the values slightly:

h1 {   border: 0.2rem solid #fff;   border-radius: 2rem;   padding: 0.4em;   box-shadow: 0 0 .2rem #fff,               0 0 .2rem #fff,               0 0 2rem #bc13fe,               0 0 0.8rem #bc13fe,               0 0 2.8rem #bc13fe,               inset 0 0 1.3rem #bc13fe; }

Notice that inset keyword? That’s something text-shadow is unable to do but adding it to the border’s box-shadow allows us to get some of the glow on both sides of the border for some realistic depth.

What about accessibility?

If users have a preference for reduced motion, we’ll need to accommodate for this using the prefers-reduced-motion media query. This allows us to remove our animation effects in order to make our text more accessible to those with a preference for reduced motion.

For example, we could modify the flashing animation from the Pen above so that users who have prefers-reduced-motion enabled don’t see the animation. Recall that we applied the flashing effect to the <h1> element only, so we’ll switch off the animation for this element:

@media screen and (prefers-reduced-motion) {    h1 {     animation: none;   } }

It’s incredibly important to ensure that users’ preferences are catered for, and making use of this media query is a great way to make the effect more accessible for those with a preference for reduced motion.


Hopefully this has shown you how to create cool neon text for your next project! Make sure to experiment with various fonts, blur radius sizes and colors and don’t forget to try out different animations, too — there’s a world of possibilities out there. And add a comment if you’ve created a neat shadow effect you want to share. Thanks for reading!

The post How to Create Neon Text With CSS appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, ,

16px or Larger Text Prevents iOS Form Zoom

This was a great “Today I Learned” for me from Josh W. Comeau. If the font-size of an <input> is 16px or larger, Safari on iOS will focus into the input normally. But as soon as the font-size is 15px or less, the viewport will zoom into that input. Presumably, because it considers that type too small and wants you to see what you are doing. So it zooms in to help you. Accessibility. If you don’t want that, make the font big enough.

Here’s Josh’s exact Pen if you want to have a play yourself.

In general, I’d say I like this feature. It helps people see what they are doing and discourages super-tiny font sizes. What is a slight bummer — and I really don’t blame anyone here — is that not all typefaces are created equal in terms of readability at different sizes. For example, here’s San Francisco versus Caveat at 16px.

San Francisco on the left, Cavet on the right. Caveat looks visually much smaller even though the font-size is the same.

You can view that example in Debug Mode to see for yourself and change the font size to see what does and doesn’t zoom.

The post 16px or Larger Text Prevents iOS Form Zoom appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , , ,