Skip to content

Lesson 4 — Breadcrumb

Breadcrumbs help users understand where they are in the app hierarchy. KanbanFlow needs them on the board show page and will need them on card detail pages in later modules.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# app/components/breadcrumb.rb
class Components::Breadcrumb < Components::Base
  def after_initialize
    @items = []
  end

  def view_template(&)
    vanish(&)

    nav(aria: { label: "Breadcrumb" }) do
      ol(class: "flex items-center gap-2 text-sm text-gray-500") do
        @items.each_with_index do |item, index|
          li(class: "flex items-center gap-2") do
            if index < @items.length - 1
              a(href: item[:url], class: "hover:text-gray-700") { item[:label] }
              span(class: "text-gray-300") { "/" }
            else
              span(class: "text-gray-900 font-medium") { item[:label] }
            end
          end
        end
      end
    end
  end

  def item(label, url: nil)
    @items << { label:, url: }
    nil
  end
end

Note: The vanish configuration pattern again. Breadcrumb uses the same approach as Table — vanish(&) yields the block early to collect configuration (the list of items) before any HTML is rendered. Each item call pushes to @items and returns nil, so nothing reaches the buffer during the vanish phase. The items are then used to render the nav in a single pass. This pattern is worth recognising: whenever a component needs to collect an ordered list of declarative inputs before rendering, vanish + a collection method is the right tool.

Usage in Views::Boards::Show:

1
2
3
4
Breadcrumb() do |b|
  b.item "Boards",    url: boards_path
  b.item @board.name
end

The last item has no url: — it renders as plain text since it represents the current page. All previous items render as links.

Add a Lookbook preview:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# test/components/previews/breadcrumb_preview.rb
class BreadcrumbPreview < Lookbook::Preview
  def two_levels
    render Components::Breadcrumb.new { |b|
      b.item "Boards", url: "#"
      b.item "My Project Board"
    }
  end

  def three_levels
    render Components::Breadcrumb.new { |b|
      b.item "Boards",             url: "#"
      b.item "My Project Board",   url: "#"
      b.item "Design column"
    }
  end
end

Update Views::Boards::Show to include the breadcrumb:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def view_template
  Breadcrumb() do |b|
    b.item "Boards", url: boards_path
    b.item @board.name
  end

  div(class: "flex items-center justify-between mt-4 mb-6") do
    h1(class: "text-2xl font-bold text-gray-900") { @board.name }
    a(href: boards_path,
      class: "text-sm text-blue-600 hover:underline") { "← Back to boards" }
  end
  # ... rest of template
end