diff --git a/app/components/DS/selectable_card.html.erb b/app/components/DS/selectable_card.html.erb
new file mode 100644
index 000000000..673758cbd
--- /dev/null
+++ b/app/components/DS/selectable_card.html.erb
@@ -0,0 +1,14 @@
+
diff --git a/app/components/DS/selectable_card.rb b/app/components/DS/selectable_card.rb
new file mode 100644
index 000000000..1ba7fe000
--- /dev/null
+++ b/app/components/DS/selectable_card.rb
@@ -0,0 +1,16 @@
+# A checkbox rendered as a selectable card: the whole card toggles, with a
+# brand-accent border + check glyph when selected. Used for the retirement
+# bucket account picker. Submits like a normal checkbox (name[]/value).
+class DS::SelectableCard < DesignSystemComponent
+ attr_reader :name, :value, :title, :subtitle, :amount, :checked, :opts
+
+ def initialize(name:, value:, title:, subtitle: nil, amount: nil, checked: false, **opts)
+ @name = name
+ @value = value
+ @title = title
+ @subtitle = subtitle
+ @amount = amount
+ @checked = checked
+ @opts = opts
+ end
+end
diff --git a/app/views/retirement/show.html.erb b/app/views/retirement/show.html.erb
index c6ba62fec..0e881ff6a 100644
--- a/app/views/retirement/show.html.erb
+++ b/app/views/retirement/show.html.erb
@@ -187,12 +187,14 @@
<%= form_with url: retirement_bucket_path, method: :patch, class: "space-y-3" do |form| %>
<% @bucket_candidates.each do |account| %>
-
+ <%= render DS::SelectableCard.new(
+ name: "bucket[account_ids][]",
+ value: account.id,
+ title: account.name,
+ subtitle: account.accountable_type&.underscore&.humanize,
+ amount: account.balance_money&.format,
+ checked: @bucket_account_ids.include?(account.id)
+ ) %>
<% end %>
<%= form.submit t("retirement.show.save_bucket"), class: "text-sm font-medium text-primary underline cursor-pointer" %>
diff --git a/test/components/DS/selectable_card_test.rb b/test/components/DS/selectable_card_test.rb
new file mode 100644
index 000000000..ca48e3870
--- /dev/null
+++ b/test/components/DS/selectable_card_test.rb
@@ -0,0 +1,25 @@
+require "test_helper"
+
+class DS::SelectableCardTest < ViewComponent::TestCase
+ test "renders a checkbox with title, subtitle, amount" do
+ render_inline(DS::SelectableCard.new(
+ name: "bucket[account_ids][]", value: "a1",
+ title: "Brokerage", subtitle: "ETF", amount: "$100,000"
+ ))
+
+ assert_selector "input[type=checkbox][name='bucket[account_ids][]'][value='a1']", visible: false
+ assert_text "Brokerage"
+ assert_text "ETF"
+ assert_text "$100,000"
+ end
+
+ test "checked renders the checkbox checked" do
+ render_inline(DS::SelectableCard.new(name: "n", value: "v", title: "T", checked: true))
+ assert_selector "input[type=checkbox][checked]", visible: false
+ end
+
+ test "unchecked omits the checked attribute" do
+ render_inline(DS::SelectableCard.new(name: "n", value: "v", title: "T", checked: false))
+ assert_no_selector "input[type=checkbox][checked]", visible: false
+ end
+end
diff --git a/test/components/previews/DS/selectable_card_preview.rb b/test/components/previews/DS/selectable_card_preview.rb
new file mode 100644
index 000000000..24fed1014
--- /dev/null
+++ b/test/components/previews/DS/selectable_card_preview.rb
@@ -0,0 +1,13 @@
+class DS::SelectableCardPreview < ViewComponent::Preview
+ # @param checked toggle
+ def default(checked: true)
+ render DS::SelectableCard.new(
+ name: "bucket[account_ids][]",
+ value: "abc",
+ title: "Vanguard FTSE All-World (VWCE)",
+ subtitle: "ETF",
+ amount: "$115,000",
+ checked: checked
+ )
+ end
+end