Card

A compound surface with typed title, body, divider, and footer slots.

Source
app/components/nitro_kit/card.rb
API
NitroKit::Card.new { |card| card.title; card.body; card.footer }

Anatomy

The card owns structural slots while headings and application content remain ordinary Phlex.

Complete structure

Title, body, divider, footer
Body only
Viewport640 px · sm
Rubytest/dummy/app/components/gallery/components/card_page.rb
sample("Title, body, divider, footer", slug: "complete") do
  render NitroKit::Card.new(id: "gallery-card-workspace") do |card|
    card.title("Mothership workspace", level: 3)
    card.body { "12 active members · Team plan" }
    card.divider
    card.footer { "Renews August 1, 2026" }
  end
end
sample("Body only", slug: "body-only") do
  render NitroKit::Card.new(id: "gallery-card-body-only") do |card|
    card.body { "A quiet surface can omit title and footer slots." }
  end
end

Content modes

Cards support full-width regions and long product content without assuming its internal markup.

Full-width region

Viewport640 px · sm
Rubytest/dummy/app/components/gallery/components/card_page.rb
render NitroKit::Card.new(id: "gallery-card-activity") do |card|
  card.title("Weekly activity", level: 3)
  card.full_width do
    card.body("Chart placeholder · 1,284 events")
  end
  card.footer("Updated July 13, 2026 at 08:42 UTC")
end

Long content

Viewport640 px · sm
Rubytest/dummy/app/components/gallery/components/card_page.rb
render NitroKit::Card.new(id: "gallery-card-long-content") do |card|
  card.title(
    "A workspace name that remains understandable when translated or supplied by a customer",
    level: 3
  )
  card.body do
    p do
      "This description deliberately spans multiple lines so the surface demonstrates natural wrapping " \
        "without a special long-content mode or application utility classes."
    end
  end
  card.footer("Last reviewed by Ada Lovelace")
end

Heading levels

The title slot emits the requested semantic heading level from one through six.

Complete title scale

Heading level 1
Heading level 2
Heading level 3
Heading level 4
Heading level 5
Heading level 6
Viewport640 px · sm
Rubytest/dummy/app/components/gallery/components/card_page.rb
(1..6).each do |level|
  sample("Heading level #{level}", slug: "heading-#{level}") do
    render NitroKit::Card.new(id: "gallery-card-heading-#{level}") do |card|
      card.title("Level #{level} title", level:)
      card.body { "Card titles follow the surrounding document hierarchy." }
    end
  end
end

Slot boundaries

Individual slots and useful partial structures remain valid without placeholder content.

Partial structures

Empty surface
Title only
Footer only
Body and footer
Full-width only
Viewport640 px · sm
Rubytest/dummy/app/components/gallery/components/card_page.rb
sample("Empty surface", slug: "empty") do
  render NitroKit::Card.new(id: "gallery-card-empty") { nil }
end
sample("Title only", slug: "title-only") do
  render NitroKit::Card.new(id: "gallery-card-title-only") do |card|
    card.title("A title without supporting content", level: 3)
  end
end
sample("Footer only", slug: "footer-only") do
  render NitroKit::Card.new(id: "gallery-card-footer-only") do |card|
    card.footer("Last synchronized July 13, 2026")
  end
end
sample("Body and footer", slug: "body-footer") do
  render NitroKit::Card.new(id: "gallery-card-body-footer") do |card|
    card.body("Three pending invitations")
    card.footer("Review access before the next billing cycle")
  end
end
sample("Full-width only", slug: "full-only") do
  render NitroKit::Card.new(id: "gallery-card-full-only") do |card|
    card.full_width { "A full-width region can be the only declared slot." }
  end
end

Record composition

Status, structured record metadata, and related actions nest without changing card anatomy.

Integration detail

Viewport640 px · sm
Rubytest/dummy/app/components/gallery/components/card_page.rb
integration = Gallery::Data.integrations.fetch(1)

render NitroKit::Card.new(id: "gallery-card-integration") do |card|
  card.title(integration.name, level: 3)
  card.body do
    render NitroKit::Badge.new(
      "Action required",
      id: "gallery-card-integration-status",
      color: :warning,
      size: :sm
    )
    p { integration.description }
    dl do
      dt { "Connected" }
      dd { integration.connected_at.iso8601 }
      dt { "Delivery target" }
      dd { "Team operations channel" }
    end
  end
  card.divider
  card.footer do
    render NitroKit::ButtonGroup.new(
      id: "gallery-card-integration-actions",
      label: "Slack integration actions"
    ) do |group|
      group.button(
        "Reconnect",
        id: "gallery-card-integration-reconnect",
        variant: :primary,
        size: :sm,
        icon: :refresh_cw
      )
      group.button(
        "Disconnect",
        id: "gallery-card-integration-disconnect",
        variant: :destructive,
        size: :sm
      )
    end
  end
end

Form composition

Card, Field, Input, and Button compose directly into a native profile form.

Profile settings

Viewport640 px · sm
Rubytest/dummy/app/components/gallery/components/card_page.rb
render NitroKit::Card.new(id: "gallery-card-profile-form-card") do |card|
  card.title("Profile", level: 3)
  card.body do
    form(id: "gallery-card-profile-form", action: "#profile", method: "post") do
      render NitroKit::Field.new(
        nil,
        :name,
        id: "gallery-card-profile-name",
        name: "profile[name]",
        value: "Ada Lovelace",
        label: "Name",
        autocomplete: "name",
        required: true,
        html: { id: "gallery-card-profile-name-field" }
      )
      render NitroKit::Field.new(
        nil,
        :email,
        as: :email,
        id: "gallery-card-profile-email",
        name: "profile[email]",
        value: "[email protected]",
        label: "Email",
        description: "Used for security notices and account recovery.",
        autocomplete: "email",
        required: true,
        html: { id: "gallery-card-profile-email-field" }
      )
    end
  end
  card.footer do
    render NitroKit::Button.new(
      "Save profile",
      id: "gallery-card-profile-save",
      type: :submit,
      form: "gallery-card-profile-form",
      variant: :primary,
      icon: :save
    )
    render NitroKit::Button.new(
      "Reset",
      id: "gallery-card-profile-reset",
      type: :reset,
      form: "gallery-card-profile-form",
    )
  end
end