Making SVG icons look right alongside text in web applications can be tricky. I'm writing down the techniques that have worked well for me, because I tend to forget them by the time I need them again.

For me it's about:

  1. preserving the icon's aspect ratio and internal spacing,
  2. scaling the icon proportionally to the text,
  3. vertically aligning the icon to the text.

Let's how these come together.

Icon proportions in isolation from accompanying text

The viewBox property defines the viewport into the SVG image. With it you can shift and crop as you like.

Consider a disc positioned at coordinates (10, 10) with a radius of 10.

<svg>
  <circle cx="10" cy="10" r="10">
</svg>

By varying the viewBox we can change the perceived zoom level and offset.

viewBox="10 5 15 15"
viewBox="-5 -5 30 30"
viewBox="0 0 20 20"

Icon designers visually balance this framing to achieve harmony within the icon set. For example the following icons come from the same set (Heroicons) but have different spacing.

Search icon Right chevron icon Folder icon

Without the viewBox information, the SVG images may appear at the wrong dimension, e.g. with the wrong "zoom level" and/or the wrong aspect ratio. If your icons don't have this property consider adding it yourself. I recommend a square aspect ratio and starting with no space between the shape and its bounding box. Then you can tweak the spacing and alignment within the SVG.

Proportional text and icons

When it comes to icon size, I first set a fallback using the width and height attributes directly on the SVG. That way if the CSS doesn't load for some reason we don't end up with the browser default, which is to render SVG images really large. I set a reasonable size, keeping the square aspect ratio, for example width="20" height="20".

Then I override the size fallback with CSS rules (or utility classes) to make the SVG image take as much space as its parent element:

svg {
  height: 100%;
  width: 100%;
}

Now I can resize the icon as needed in different contexts without modifying the SVG source or targeting the icon specifically with CSS rules.

For example, if I'm making a form button containing the search icon:

I would wrap the icon in a div and set the div element's height (inline style for illustration here, I would do it differently in real application code).

<button type="submit">
  <div style="height: 1rem; width: 1rem;">
    <!-- search icon rendered here -->
  </div>
  Search
</button>

The key is to use units proportional to the text size, like em or rem, for the height of the wrapper element. That way the icon scales with the text.

Aligning icons with multi-line text

For the button above, I centered the icon and text vertically in the button using flexbox.

button[type="submit"] {
  display: inline-flex;
  align-items: center;
}

This works well with a single line of text, but may not be what you want when the text spans multiple lines.

In the following example, the icon is centered vertically.

Message title
A long and detailed message description.
Flex children aligned with align-items: center

What if we wanted to align it with the first line? In my opinion both align-items: start and align-items: baseline look off.

Message title
A long and detailed message description.
Aligned with align-items: start
Message title
A long and detailed message description.
Aligned with align-items: baseline

The trick is to have a sibling text node to which you can centrally align the icon (like in the single line case), but then use baseline alignment on the parent, so that the text from the two columns aligns. Let's visualize that before getting to the code:

reference 
Message title
A long and detailed message description.
Two columns aligned with align-items: baseline and an icon aligned to the text in the first column with align-items: center

The icon is centrally aligned to the text which says "reference". "reference" is baseline-aligned to "Message title".

Of course you don't want actual text next to the icon. The trick is to use a zero-width space, which you can insert by writing its HTML entity: &#x200B;. Normal white space does not work because HTML parsers strip that.

Check out the simplified code. (Again the styles are inline to make it easier to understand what is going on)

<div style="display: flex; align-items: baseline">
  <div style="display: flex; align-items: center">
    &#x200B; <!-- zero-width space, for alignment -->
    <div style="height: 1em; width: 1em;">
      <!-- SVG icon goes here, omitted for brevity -->
    </div>
  </div>
  <div>
    <!-- text from the right column goes here -->
  </div>
</div>

Here's the result:

Message title
A long and detailed message description.

It's easier to compare with larger icons:

Message title
A long and detailed message description.
Aligned with align-items: baseline
Message title
A long and detailed message description.
Aligned with align-items: center at the inner level and align-items: baseline at the outer level.

In my projects, I usually encapsulate the necessary markup in a function or component. For example, I have this view helper in a Rails project (the utility classes are from Tailwind CSS):

def inline_icon(icon_name, class: 'w-4')
  capture do
    content_tag(:div, class: 'flex items-center') do
      concat '&#x200B;'.html_safe # zero-width space character to set the correct height (in combination with a 100% height on the svg icon itself)
      concat (content_tag(:div, class: binding.local_variable_get(:class)) do
        render "icons/#{icon_name}"
      end)
    end
  end
end