diff --git a/lib/phlexy_ui/list.rb b/lib/phlexy_ui/list.rb
new file mode 100644
index 0000000..1c0d557
--- /dev/null
+++ b/lib/phlexy_ui/list.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module PhlexyUI
+ # @component html class="list"
+ class List < Base
+ def initialize(*, as: :ul, **)
+ super(*, **)
+ @as = as
+ end
+
+ def view_template(&)
+ generate_classes!(
+ # "list"
+ component_html_class: :list,
+ modifiers_map: modifiers,
+ base_modifiers:,
+ options:
+ ).then do |classes|
+ public_send(as, class: classes, **options, &)
+ end
+ end
+
+ def row(**options, &)
+ generate_classes!(
+ # "list-row"
+ component_html_class: :"list-row",
+ options:
+ ).then do |classes|
+ li(class: classes, **options, &)
+ end
+ end
+
+ def col_wrap(as: :div, **options, &)
+ generate_classes!(
+ # "list-col-wrap"
+ component_html_class: :"list-col-wrap",
+ options:
+ ).then do |classes|
+ public_send(as, class: classes, **options, &)
+ end
+ end
+
+ def col_grow(as: :div, **options, &)
+ generate_classes!(
+ # "list-col-grow"
+ component_html_class: :"list-col-grow",
+ options:
+ ).then do |classes|
+ public_send(as, class: classes, **options, &)
+ end
+ end
+ end
+end
diff --git a/spec/lib/phlexy_ui/list_spec.rb b/spec/lib/phlexy_ui/list_spec.rb
new file mode 100644
index 0000000..243db40
--- /dev/null
+++ b/spec/lib/phlexy_ui/list_spec.rb
@@ -0,0 +1,149 @@
+require "spec_helper"
+
+describe PhlexyUI::List do
+ subject(:output) { render described_class.new }
+
+ it "is expected to match the formatted HTML" do
+ expected_html = html <<~HTML
+
+ HTML
+
+ is_expected.to eq(expected_html)
+ end
+
+ describe "with row method" do
+ subject(:output) do
+ render described_class.new do |l|
+ l.row { "Row 1" }
+ l.row { "Row 2" }
+ end
+ end
+
+ it "renders rows" do
+ expected_html = html <<~HTML
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+
+ describe "with col_wrap method" do
+ subject(:output) do
+ render described_class.new do |l|
+ l.row do
+ l.col_wrap { "Wrapping content" }
+ end
+ end
+ end
+
+ it "renders col_wrap inside row" do
+ expected_html = html <<~HTML
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+
+ describe "with col_grow method" do
+ subject(:output) do
+ render described_class.new do |l|
+ l.row do
+ l.col_grow { "Growing content" }
+ end
+ end
+ end
+
+ it "renders col_grow inside row" do
+ expected_html = html <<~HTML
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+
+ describe "with col_grow custom element" do
+ subject(:output) do
+ render described_class.new do |l|
+ l.row do
+ l.col_grow(as: :span) { "Growing content" }
+ end
+ end
+ end
+
+ it "renders col_grow as specified element" do
+ expected_html = html <<~HTML
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+
+ describe "data" do
+ subject(:output) do
+ render described_class.new(data: {foo: "bar"})
+ end
+
+ it "renders it correctly" do
+ expected_html = html <<~HTML
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+
+ describe "with both col_wrap and col_grow in same row" do
+ subject(:output) do
+ render described_class.new do |l|
+ l.row do
+ l.col_grow { "Growing content" }
+ l.col_wrap { "Wrapping content" }
+ end
+ end
+ end
+
+ it "renders both column types in one row" do
+ expected_html = html <<~HTML
+
+ -
+
Growing content
+ Wrapping content
+
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+
+ describe "passing :as option" do
+ subject(:output) { render described_class.new(as: :ol) }
+
+ it "renders as the given tag" do
+ expected_html = html <<~HTML
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+end