Skip to content

Lesson 6 — The Table component: vanish for configuration

Named slots use vanish to collect content. The Table component uses vanish for something subtly different — collecting configuration.

The column-based API

We want to describe a table by its columns rather than its HTML structure:

1
2
3
4
Table(rows: PEOPLE).call { |t|
  t.column("Name") { |row| row[:name] }
  t.column("Role") { |row| row[:role] }
}

The block does not produce HTML — it calls column to register definitions which are then used to render both the headers and the rows in separate passes.

How t populates @columns — the key insight

t is not a proxy or configuration object. t is the Table instance itself. Phlex automatically upgrades yield to yield(self), so the block receives the component. Calling t.column(...) is simply calling the column instance method on that component:

1
2
3
4
def column(header, &content)
  @columns << { header:, content: }
  nil   # return nil — no HTML output here
end

Each call pushes directly onto @columns on that same instance. By the time vanish returns, @columns is fully populated — not by magic, but by direct method calls on a live Ruby object.

The block is not configuration data being parsed — it is Ruby code executing methods on a live component instance. This is true of the Card slots too: card.header { ... } calls the header method on the Card instance. t.column(...) calls the column method on the Table instance. The pattern is identical — the block argument is always the component itself.

Why column returns nil

Unlike named slots where content is captured once for later output, column definitions are used twice — once for <thead> headers and once per row in <tbody>. Returning nil ensures nothing reaches the buffer during the vanish phase.

The full Table component

Pico styles <table> cleanly with no classes needed:

 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
31
32
33
34
35
36
37
38
39
40
# app/components/table.rb
require_relative "base"

module Components
  class Table < Base
    prop :rows,    _Array(_Any)
    prop :caption, _Nilable(String), default: nil

    def after_initialize
      @columns = []
    end

    def view_template(&)
      vanish(&)

      table do
        caption { @caption } if @caption
        thead do
          tr do
            @columns.each { |col| th { plain { col[:header] } } }
          end
        end
        tbody do
          @rows.each do |row|
            tr do
              @columns.each do |col|
                td { plain { col[:content][row].to_s } }
              end
            end
          end
        end
      end
    end

    def column(header, &content)
      @columns << { header:, content: }
      nil
    end
  end
end

Adding Table to the demo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
require_relative "app/components/table"

DEMO_PEOPLE = [
  { name: "Alice", role: "Admin"  },
  { name: "Bob",   role: "Member" },
  { name: "Carol", role: "Member" },
]

def show_tables
  section_header("Tables")
  Table(rows: DEMO_PEOPLE, caption: "Team members") do |t|
    t.column("Name") { |row| row[:name] }
    t.column("Role") { |row| row[:role] }
  end
end

Exercise

Extend show_tables to render a second table listing your Phlex::UI components, with “Component” and “Status” columns. Use a Badge inside the status column.


Solution
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
COMPONENTS_LIST = [
  { name: "Button",  status: "Done" },
  { name: "Badge",   status: "Done" },
  { name: "Avatar",  status: "Done" },
  { name: "Card",    status: "Done" },
  { name: "Table",   status: "Done" },
]

def show_tables
  section_header("Tables")

  Table(rows: DEMO_PEOPLE, caption: "Team members") do |t|
    t.column("Name") { |row| row[:name] }
    t.column("Role") { |row| row[:role] }
  end

  Table(rows: COMPONENTS_LIST, caption: "Phlex::UI components") do |t|
    t.column("Component") { |row| row[:name] }
    t.column("Status") { |row| Badge(label: row[:status], variant: :success) }
  end
end

The completed component library and demo page can be downloaded here: phlex-sandbox.