Nav Styled with SVG Icons

OK, it’s time to quit messing around and get this site ready for visitors. Number one priority primo uno is getting the site navigation styled. Up until now, it was just an unordered list of links. I had ideas…

  1. Add some icons.
  2. Set the nav at the page bottom with fixed position for mobile-friendly UX.

…OK, not very many ideas. 😅

Finding icons

This part was a snore. I saw Bootstrap in the results when I Googled “svg icons”. From previous experience with Bootstrap, I knew it would be reliable in terms of quality and selection. I also knew it would be free without hassle. What I wasn’t sure about is how easy it would be to get the individual SVG files. Rest assured: easy peasy.

The Bootstrap icon library has thousands of SVG icons and you can filter by keywords to easily find what you are looking for. Click on the one you like to get details and a big “Download SVG” button is there at the top of the page: done! 😎

https://icons.getbootstrap.com/

Adding color

This part was not a snore. I naively thought you could just add an <img> tag with an SVG src and fill it with color. Oh boy, was I mistaken.

// NavSite.astro
<img class="wrench-svg" src="/img/wrench.svg" />
<style> .wrench-svg { fill: #69f; } </style>

If only life was so simple, you fool. 👎

I tried some other things.

I noticed in the SVG markup, it had this attribute: fill="currentColor". Does this mean that I can just add color CSS property instead of fill? No, try again. 😑

I can add the SVG as a background image and then maybe get more flexibility with some of the background-* CSS properties? No, try again. 😑

Eventually, I decided I should read up on SVGs because the way to style these things is not obvious. All the styling info I found had embedded SVG in the markup. I mean, come on, what? How is that going to be flexible or reusable if it’s pasted into every single place I want to use it? OK, whatever. Let’s keep going and see if it will take color:

// NavSite.astro
<svg class="wrench-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
  <path d=" ... "/>
</svg>
<style> .wrench-svg { fill: #69f; } </style>

Booyah! It works! 🎉

But I’m really not happy about having this SVG source code embedded in the page. Let’s see if there’s anything to be done about it…

Using the <use> tag

From some past experience with SVG, I knew you could clone nodes inside the SVG with the <use> tag. But can you link to an external SVG with a <use> tag? After some initial missteps and head scratching, I eventually found: yes, this is possible. There is just one critical concept that took me a while to suss out:

  1. You must have an id attribute on the node of the external SVG you want to clone into your <use> tag.
  2. You must reference that identifier when you link to the external SVG in your <use> tag.

Let me explain. In the external wrench SVG, I want to clone the <path> element that actually draws the shape of the icon. So, I add the id="wrench" attribute to it:

<!-- /img/wrench.svg -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
  <path id="wrench" d=" ... "/>
  <!--      ^^^^^^ -->
</svg>

Now, we make another <svg> tag with a child <use> that points to our external SVG file via href:

// NavSite.astro
<svg>
  <use href="/img/wrench.svg#wrench"/>
  {/*                        ^^^^^^ */}
</svg>

Eureka! 💡

After this, I realized that I could:

  1. Create one SVG file and paste all the icon shapes into it.
  2. Add identifiers to each icon <path> or <g> (group) that I wanted to use.
  3. Reference those identifiers in the <use> tag.

Example:

<!-- img/nav-site.svg -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
  <path id="hexagon" d=" ... "/>
  <path id="history" d=" ... "/>
  <g id="puzzle"> ... </g>
</svg>
<!-- NavSite.astro -->
<a href="/">
  <svg><use href="/img/nav-site.svg#hexagon"/></svg> Home
</a>
<a href="/history">
  <svg><use href="/img/nav-site.svg#history"/></svg> History
</a>
<a href="/skills">
  <svg><use href="/img/nav-site.svg#puzzle"/></svg> Skills
</a>

Everything is going pretty good but I’m having trouble scaling the icons. They are all stuck at 16x16… 🧐

The importance of viewBox

To scale SVGs with CSS, be sure to add the viewBox attribute. Otherwise, the SVG will lock into the viewBox sizes inherited from the linked SVG document.

The final JSX looked something like this:

// NavSite.astro
<nav>
  <ul>
    {navItems.map(o => (
      <li class={kebabCase(o.href) || 'home'}><a href={o.href}>
        <svg aria-hidden="true" role="img" viewBox="0 0 16 16">
          <use href={o.iconHref}/>
        </svg><br>
        {o.text}
      </a></li>
    ))}
  </ul>
</nav>