Skip to main content

How to hide decorative anchor

609 words (approximately 3 minutes reading)

As you might have noticed, I recently have some changes to the website design. The changes are mostly for accessibility and readability: the text is now bigger so it’s easier to read and select. I also color visited links, so people can spend less time knowing they’ve read a link (haha who wants people to spend less time on their site I must be crazy right?). One of the changes it the heading anchor with a decorative “#” to make visual readers recognize the heading levels easier. However, if this would probably be announced as “hash” by screen readers, which wouldn’t make sense. In this post, I’d show how I ended up with the current one.

Simple heading anchor

Heading anchor can simply be done by wrapping the content inside the anchor element, <a>, which refers to the ID of the heading:

<h2 id="foo">
  <a href="#foo">Foo</a>

This is very simple, and it works perfectly fine. However, I also would like to have a fancy visual cue in front of the heading some website use a chain link, but I prefer to use a simple character instead of an image. The hash symbol makes sense for me, since I mostly use Markdown. I use heading level minus 1 hashes for this (since heading level one is not used within a post). Using ::before pseudo-element, this is rather simple:

h2::before {
  content: '# ';
h3::before {
  content: '## ';

Unfortunately, this leads to an accessibility problem as stated at the beginning of this post: screen readers (inconsistently) announces this as “hash”. Multiply that with the level of heading and imagine the nuisance.

There are several proposals to solve this, which is discussed in following sections.

Alternative text for pseudo-element

I’ve read from some StackOverflow answers1 that you can add alternative text for pseudo-element by doing this:

h2::before {
  content: '# ';
  content: '# ' / '';

I don’t know which browser supports this; it looks like bad syntax. The answer itself said this is non-standard. And I’d tell you this is not supported by Firefox, at least.

Using aria-hidden content and visually hidden description

So, I found a blog post whose content is similar to this one. It is likewise a long post, but in short the method is instead of using a pseudo-element, you can use aria-hidden decorative element with a visually hidden text. It looks like this:

<h2 id="foo">
  <a href="#foo">
    <span aria-hidden="true">#</span>
    <span class="visually-hidden">Section titled Foo</span>

This method is also used by HTMHell and MDN’s social icons (though, HTMHell call the class u-hidden and use sr-only in their instruction).

Here is how MDN styles the visually hidden class:

.visually-hidden {
  border: 0;
  clip: rect(0 0 0 0);
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute !important;
  width: 1px;

This, however, looks bad if you read my posts from a RSS reader, as a RSS reader may not support all HTML tag and doesn’t have the CSS for the visually hidden element at all. The heading appears as “# Section titled Foo Foo”, which is rather hideous, if you ask me.

Current solution

It is a rather simple combination of the previous approach and my original approach: use aria-hidden decorative icon, and use ::before pseudo-element to avoid it rendering in the RSS feed.


<h2 id="foo">
  <span class="decorative" aria-hidden="true"></span>
  <a href="#foo">Foo</a>


h2 .decorative::before {
  content: '# ';

I have only tested this on Firefox with orca as screen reader, though I expect it to do well on others as well.

  1. one of a rather less reliable source of knowledge, yet commonly used by many people ↩︎


Look at my fedi fellows' sites:
  1. Previous site
  2. What is Fediring?
  3. Next site

Articles from blogs I read

Generated by fead