diff --git a/app/helpers/tab_bar_helper.rb b/app/helpers/tab_bar_helper.rb
new file mode 100644
index 00000000..febae158
--- /dev/null
+++ b/app/helpers/tab_bar_helper.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module TabBarHelper
+  def tab_bar_item(item)
+    content_tag(
+      :li,
+      safe_join([li_icon(item), li_content(item)]),
+      class: li_class(item)
+    )
+  end
+
+  private
+
+  def li_content(item)
+    if item[:link]
+      link_to item[:title], item[:link]
+    else
+      item[:title]
+    end
+  end
+
+  def li_class(item)
+    'jam-tab-bar__selected' if item[:selected]
+  end
+
+  def li_icon(item)
+    content_tag(:span, icon_svg.html_safe, class: 'icon') if item[:icon] # rubocop:disable Rails/OutputSafety
+  end
+
+  def icon_svg
+    %(
+    <svg width="0.75em" height="0.75em" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+      <path d="M14 8.5H8V14.5H6V8.5H0V6.5H6V0.5H8V6.5H14V8.5Z" fill="#F16B7C"/>
+    </svg>
+    )
+  end
+end
diff --git a/app/views/shared/_tab_bar.html.erb b/app/views/shared/_tab_bar.html.erb
new file mode 100644
index 00000000..4e7ba8a4
--- /dev/null
+++ b/app/views/shared/_tab_bar.html.erb
@@ -0,0 +1,9 @@
+<div class="jam-tab-bar">
+  <ul class="switcher">
+    <% if local_assigns[:items] %>
+      <% items.each do |item| %>
+        <%= tab_bar_item(item) %>
+      <% end %>
+    <% end %>
+  </ul>
+</div>
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb
index 58d4aef9..69713b1c 100644
--- a/app/views/users/show.html.erb
+++ b/app/views/users/show.html.erb
@@ -1,22 +1,9 @@
 <%= render('shared/box', title: 'My account') do %>
-  <div class="jam-tab-bar">
-    <ul class="switcher">
-      <li class="jam-tab-bar__selected">Personal</li>
-      <% Current.user.artists.each do |artist| %>
-        <li><%= link_to artist.name, edit_artist_path(artist) %></li>
-      <% end %>
-      <% if policy(Artist).new? %>
-        <li>
-          <span class="icon">
-            <svg width="0.75em" height="0.75em" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
-              <path d="M14 8.5H8V14.5H6V8.5H0V6.5H6V0.5H8V6.5H14V8.5Z" fill="#F16B7C"/>
-            </svg>
-          </span>
-          <%= link_to "Add artist", new_artist_path %>
-        </li>
-      <% end %>
-    </ul>
-  </div>
+  <%= render('shared/tab_bar', items: [
+    { title: 'Personal', selected: true },
+    Current.user.artists.map { |artist| { title: artist.name, link: edit_artist_path(artist) } },
+    ( { title: 'Add artist', link: new_artist_path, icon: true } if policy(Artist).new? )
+  ].flatten.compact) %>
 
   <ul>
     <li><%= link_to "Change password", edit_password_path, class: 'link' %></li>
diff --git a/test/components/previews/tab_bar_preview.rb b/test/components/previews/tab_bar_preview.rb
new file mode 100644
index 00000000..c5252441
--- /dev/null
+++ b/test/components/previews/tab_bar_preview.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class TabBarPreview < Lookbook::Preview
+  def default
+    render('shared/tab_bar', items: [
+             { title: 'First', selected: true },
+             { title: 'Second', link: 'https://example.com' },
+             { title: 'Third', link: 'https://example.com', icon: true }
+           ])
+  end
+end
diff --git a/test/helpers/tab_bar_helper_test.rb b/test/helpers/tab_bar_helper_test.rb
new file mode 100644
index 00000000..937dfd35
--- /dev/null
+++ b/test/helpers/tab_bar_helper_test.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'test_helper'
+
+class TabBarHelperTest < ActionView::TestCase
+  test 'tab_bar_item adds selected class if item is selected' do
+    assert_dom_equal '<li class="jam-tab-bar__selected"></li>', tab_bar_item({ selected: true })
+  end
+
+  test 'tab_bar_item does not add selected class if item is not selected' do
+    assert_dom_equal '<li></li>', tab_bar_item({})
+  end
+
+  test 'tab_bar_item uses provided title' do
+    assert_dom_equal '<li>foo</li>', tab_bar_item({ title: 'foo' })
+  end
+
+  test 'tab_bar_item links to provided title if link provided' do
+    assert_dom_equal(
+      '<li><a href="https://example.com">foo</a></li>',
+      tab_bar_item({ title: 'foo', link: 'https://example.com' })
+    )
+  end
+
+  test 'tab_bar_item can include icon' do
+    expected = %(
+    <li>
+      <span class="icon">
+        <svg width="0.75em" height="0.75em" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+          <path d="M14 8.5H8V14.5H6V8.5H0V6.5H6V0.5H8V6.5H14V8.5Z" fill="#F16B7C"/>
+        </svg>
+      </span>
+      <a href="https://example.com">foo</a>
+    </li>
+    )
+
+    assert_dom_equal(
+      expected,
+      tab_bar_item({ title: 'foo', link: 'https://example.com', icon: true })
+    )
+  end
+end