skip to Main Content

I have the following code:

<h2>
    Deadpool
    <svg width="53" height="64" class="inline" aria-label="and">
        <use href="#oleo-ampersand"></use>
    </svg>
    Wolverine:<br>
    A StackOverflow Example
</h2>

The ampersand is stylized as follows:

enter image description here

Now, I want screen readers to narrate it as a regular phrase (i.e.“Deadpool and Wolverine”). Adding aria-label="and" to the <svg> seems to be ignored. Is there a way to achieve this? Maybe, I should make the whole block aria-hidden and add a visually hidden element instead?

Also, it would be cool to make the block being copied the same way on Ctrl+C, but I guess it’s not possible without a JS intercepting.

3

Answers


  1. It is indeed not trivial to copy the text without JS.

    Does this markup work for you on the screen reader?

    document.addEventListener('copy', (event) => {
      const selection = window.getSelection().toString();
      if (selection.includes("Deadpool") && selection.includes("Wolverine")) {
        event.clipboardData.setData('text/plain', "Deadpool and Wolverine");
        event.preventDefault();
      }
    });
    .sr-only {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border: 0;
    }
    <h2>
      <span class="sr-only">Deadpool and Wolverine</span> Deadpool
      <svg width="53" height="64" class="inline" aria-hidden="true">
            <use href="#oleo-ampersand"></use>
        </svg> Wolverine:
      <br> A StackOverflow Example
    </h2>
    Login or Signup to reply.
  2. You may also apply the .sr-only class only to a hidden <span> with the text content "and".

    If your SVG doesn’t contain any text elements you may even omit the aria-hidden attribute.

    To avoid browser inconsistencies when copying the text we can:

    • apply aria-hidden="true" to the SVG element (containing the ampersand)
    • add a hidden but selectable/readable <span>

    You as well use a span with a subset version of the desired font applied (1. example). In this case you would need to apply aria-hidden="true" and make it unselectable via user-select:none

    body {
      font-family: sans-serif;
    }
    
    /** subset version of Oleo - only including ampersans **/
    @font-face {
      font-family: "Oleo Script Swash Caps";
      font-style: normal;
      font-weight: 700;
      font-display: swap;
      src: url("data:font/woff2;base64,d09GMgABAAAAAAMMAA8AAAAABfAAAAK5AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbDBwYBmAAPBEICoFggV0LCAABNgIkAwwEIAWEfAcgDAcb4wTIBB74POv9JAM4Y67Agdzuul3iavAk5uevyUfH3wZyAYCR78cyKJoaK2w+kQLK4JlOZAPjqcYWu4QOsXcpavUIwBoHhE2kg/aBWBrGDVylURSkVhRhTm6WJQ/pECYgGaSRSvucZGI8CcIiLJE4OpkN5elMSW1ZOSoqIfKn/1OtivxJ1KtVFBEHpY+GqKS242RwViIUKYRxplojyyohmkaqJqM91x5lNzqR0TTsmqgfmOkF+oJNJDxK/f6z3M7MPHuxzt3ScEt33Xg38JzhgOfSjW6rjXfv3jXe1t0y9L91y9LQ+87wLfoud0ecuSP0d4230V0zXNG32XVMdzw5Vb/B0POy8cFKves6dIs3bonwTzPtPKrf9N9ZfUZtbOi6R+eaP88zrFto15gRoYM7JUzs3KkoVGQsXVHU5WIvP59OSml8p1F1yriu/Vr0aRy+uqRH2/Da/kVD2tzo3//b3sjZ1ilxW1P6fA++E/zps6/VIwBo6TXB5NerCidPsGv7zdxKfofcpTPnI04HtdT/U5UA+RMC897Hore1Pm9TOmqpmqkS0GNW4pLFV5xzkpYu0OlHXGbsKJvjYcwNT0c2bk7W6PgUC03dER1vgiBKOAsS5sJakBnJLkEhhSbBBE8yBFPiSAJbvAM5VFBBCa2JJZZy0ikjlxIqBMVQ/ssCYiimjGxiGUB3+jKAAjIpZgipyOZqUiknhy6kUoJN0XSmmAIyGG5IWTGviilCJZ4Y4ogjAdOqTKrqUZVXv4shFFNLBZVUUEsJmbRGDbRMrQnEEU/C68gKgS5IlFBLkOyBXKwSRjrhRZKobD2bazDaYHy6glTSqaAj5QGJASokZl4ZDYh1JBssdZIMkoRXF6smF1pDY2VkQpuAYhWZZIBlVQpFSgob4gntMgtd12uh7SaBpHqf0KZuU2YD")
        format("woff2");
    }
    
    .ampersand {
      font-family: "Oleo Script Swash Caps";
      font-weight: 700;
    }
    
    
    /* SVG ampersand */
    .svgText {
      height: 1em;
      transform: scale(1.5) translateY(0.1em);
    }
    
    
    .non-select{
        user-select:none
    }
    
    /* hide for not impaired readers */
    .sr-only {
      clip-path: inset(50%);
      height: 1px;
      overflow: hidden;
      position: absolute;
      white-space: nowrap;
      width: 1px;
      outline:1px solid #ccc
    }
    
    textarea {
      display: block;
      width: 100%;
      min-height: 10em;
    }
    
    h2{
      white-space: nowrap;
    }
    <h2>
        Deadpool <span aria-hidden="true" class="non-select  ampersand">&</span><span class="sr-only">and</span>
        Wolverine
    </h2>
    
    <h2>
    Deadpool <svg class="svgText" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 75 138.3"  aria-hidden="true"><path class="glyph" d="M36.2 57.3 L36.3 57.3 Q36.6 58.4 36.6 60.1 Q36.6 61.7 35.9 63.6 L35.9 63.6 L35.3 63.6 Q28.7 63.6 24.2 67.5 Q19.7 71.4 19.7 78.2 Q19.7 84.9 22.9 87.9 Q26.1 90.8 31.2 90.8 Q36.3 90.8 40.1 86.3 Q43.9 81.8 45 75.8 L45 75.8 Q39.4 78.7 36 78.7 Q32.6 78.7 30.4 77.6 Q28.2 76.4 27.3 74.7 L27.3 74.7 Q27.5 74 28.6 72.6 Q29.8 71.2 30.8 71.1 L30.8 71.1 Q32.6 72.2 34.6 72.2 Q36.6 72.2 40.1 70.2 Q43.6 68.2 47.1 65.8 Q50.5 63.5 54.9 61.5 Q59.3 59.5 63.4 59.5 Q67.5 59.5 70.8 62.1 Q74.1 64.6 74.1 69.4 Q74.1 74.2 70.8 77.5 Q67.4 80.8 61.9 80.8 Q56.4 80.8 51.3 76.8 L51.3 76.8 Q51.1 86.9 43.5 94.7 Q35.9 102.4 24.9 102.4 L24.9 102.4 Q18.2 102.4 12.1 99.6 L12.1 99.6 Q4.7 96.4 1.7 89.8 L1.7 89.8 Q0 86.1 0 80.5 Q0 74.8 3.6 69.8 L3.6 69.8 Q10.2 60.8 22.5 60.3 L22.5 60.3 Q17.1 58.9 13.7 55 Q10.2 51.1 10.2 45.6 L10.2 45.6 Q10.2 38.1 16.5 33.8 Q22.8 29.4 31.1 29.4 Q39.3 29.4 45 33 Q50.6 36.6 50.6 42.6 L50.6 42.6 Q50.6 46.7 47.8 49.6 Q45 52.5 41.1 52.5 Q37.2 52.5 34.5 50.2 Q31.7 47.8 31.7 43.7 Q31.7 39.5 35.3 36.7 L35.3 36.7 Q30.7 37 28.9 40.2 Q27 43.3 27 47.2 Q27 51.1 29.4 53.8 Q31.8 56.5 36.2 57.3 L36.2 57.3 ZM66.2 71.5 L66.2 71.5 Q66.2 70.3 65.2 69.2 Q64.2 68 61.8 68 Q59.4 68 53.5 71.2 L53.5 71.2 Q58.7 75.1 62.5 75.1 Q66.2 75.1 66.2 71.5 ZM40.6 45.7 Q41.5 45.7 42.2 44.6 Q42.9 43.4 42.9 41.6 Q42.9 39.7 41.4 38.4 L41.4 38.4 Q38.2 40.3 38.2 43.3 L38.2 43.3 Q38.2 44.4 38.9 45.1 Q39.6 45.7 40.6 45.7 Z " /></svg><span class="sr-only">and</span> Wolverine
    </h2>
    
    <h3>Paste text</h3>
    <textarea></textarea>

    The commonly used sr-only rules ensure the text is still readable by screen-readers and selectable – although invisible for sighted users.
    The main trick of this technique is to make the element invisible by multiple properties without applying disply:none or visibility:hidden as both properties would remove the content from the accessibility-tree.

    See also "The A11Y Project: Hide content"

    SVG to font conversion

    If your inline SVG graphic is not available as a font – you may also use a SVG-to-font-generator like icomoon or fontello to convert your assets to an icon font.

    Subsetting

    If your font service doesn’t support subsetting you may also convert it with a service like transfonter. It also has an option to generate base64 encoded dataURLs of the font resource.

    Login or Signup to reply.
  3. Maybe, I should make the whole block aria-hidden and add a visually hidden element instead?

    Yes, remove the svg from the accessibility tree and add the word "and" as visually hidden text.

    <h2>
      <span class="first-line">
        Deadpool
        <svg width="53" height="64" class="inline" aria-hidden="true">
          <use href="#oleo-ampersand"></use>
        </svg>
        <span class="sr-only">and&nbsp;</span> 
        Wolverine:
      </span> 
      A StackOverflow Example
    </h2>
    
    .first-line {
      display: block;
    }
    

    Also, notice that I removed the br element after "Wolverine" and instead wrapped they first line of content in a span and gave it a display of block to get the line break you want. In browse mode, some screen readers will announce the heading as if it were two separate headings if you use the br element to force a line break, so this is a courtesy to prevent that.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search