BLOG
Bootstrap Icon Packages
Bootstrap also has an icon package. It’s great to use because, at the start of many projects, many clients haven’t really decided on an icon pack yet, and while the bootstrap icons are nothing fantastical, they are, like the bootstrap CSS a workhorse. They don’t have everything, and there are more complete packs, but bootstrap icons are clean, look good small or large, and will work as a stand-in (or fallback) for quite some time on greenfield projects.
Including the icons and SVG
So this is a bit of a hiccup. I don’t want to include any CSS from bootstrap (or any of its javascript or other assets) in the project. But I do feel it’s necessary to include the SVG files for the icons. Again my goal is to have these icons as a fallback or default on projects where people haven’t picked an icon pack yet. That can be complicated if you don’t include some icons.
The other objective
Remember that our goal is not to tell people they have to do it one way or another but in this modification were adding in a default. So we need that default to be easy to override.
A note on the method
Before we dive in, I would like to address the way I access the icons. It’s not the fastest, there are faster ways. But not in ways that don’t mean including CSS. Making it more complicated, CSS should be able to override other CSS, but where font-based icons are concerned that’s rarely the case. Using SVG files may be a tiny bit slower, but it’s much easier to use, and should not have a measurable performance hit on modern host operating systems. I will explain a bit more later.
Getting the icons
This time we’re going to create a script to download the bootstrap icons and extract just the SVG files, then place those files in the proper directory. Again, there are other ways to do this. Git submodules for example. But I don’t want the entire project or even most of it. Just the SVG files.
#!/bin/bash
# scripts/update_icons.sh
FIRST=`pwd`
cd /tmp/
git clone https://github.com/twbs/icons.git
cd icons/icons/
cp *.svg $FIRST/icons/
cd $FIRST
The helper and the class
The helper is very simple and just calls ‘BootstrapHelpers::Icon.render’. Let’s look at the class:
def self.render(name)
if File.exist?("#{Rails.root}/app/icons/#{name}.svg")
File.open("#{Rails.root}/app/icons/#{name}.svg").read.html_safe
else
File.open("#{__dir__}/../../icons/#{name}.svg").read.html_safe
end
rescue Errno::ENOENT
'X'.html_safe
end
def self.enumerate
output = []
Dir::foreach(__dir__ + '/../../icons/') do |filename|
next if filename == '.' || filename == '..'
next if File.directory? filename
output << File.basename(filename, File.extname(filename))
end
output
end
‘render’ is doing the heavy lifting it checks the project ‘app/icons’ directory for an svg that matches, and if found uses it. If not found, it returns to using the ones included in the gem (bootstrap icons). Finally, if it errors, due to a missing file (or likely a bad name), then it just shows a plain old X.
‘enumerate’ is just a way to list out what icons are available.
<%= row do %>
<%= col size: 1 do %>
<p class='text-center text-<%= %w[success warning danger info].sample %>'>
<%= icon 'wsssjsjs' %><br/>
Can't Exist
</p>
<% end %>
<% @names.each do |name| %>
<%= col size: 1 do %>
<p class='text-center text-<%= %w[success warning danger info].sample %>'>
<%= icon name %><br/>
<%= name %>
</p>
<% end %>
<% end %>
<% end %>
Our sample icons page, has an example of an icon that can’t exist, and iterates over the known icons and displays them.
@names = BootstrapHelpers::Icon.enumerate
The controller is quite simple. That sample
call just randomizes the color a bit to make the page more interesting.
Spreading the icons around
Let’s actually use the icons. First let’s add some icons to the navbar.
<!-- /app/views/layouts/_main_navigation.html.erb -->
<%= navbar classes: 'bg-dark navbar-expand-lg navbar-dark ps-4' do |nav| %>
<%= nav.brand content: "#{icon('todo')} Todo".html_safe %>
<%= nav.toggler target: 'mainMenu' %>
<%= nav.collapse id: 'mainMenu' do |c| %>
<%= nav.items class: 'ms-auto' do |i| %>
<%= i.link "#{icon('list-task')} Tasks".html_safe, items_path, class: 'btn btn-outline-secondary' %>
<%= i.link "#{icon('plus-square')} New Task".html_safe, new_item_path, class: 'btn btn-outline-primary ms-4 me-4' %>
<% end %>
<% end %>
<% end %>
A very clean way to add an icon. And if you look at the repository you will notice were using a custom icon ‘todo’ (though it’s still just an SVG from bootstrap icons)
Next let’s use the new icon helpers in the alert and flash helpers.
def generate_icon(icon, level)
return '' unless icon
return icon.html_safe if icon.is_a? String
case level.to_s
when 'success'
Icon.render('check-circle-fill')
when 'info'
Icon.render('info-circle-fill')
else
Icon.render('exclamation-triangle-fill')
end
end
That is much more maintainable, and more extendable. Keep in mind that if you want to use font-awesome or some other icon pack you can. You can either put their svgs in /app/icons
or just pass in the normal markup.
Conclusions
While I don’t like including so many files, if we’re going to have a fallback icon pack, then bootstrap icons are a good choice. Reading from the file to output a string also seems odd, but it’s the best way to tackle this. It keeps the files out of the asset pipeline, which seems like an odd goal, but you don’t want them in your pipeline if you’re never going to use them.