Card

Card is a presentational container for grouping related content: a heading, supporting text, a body, and an optional footer.

Install

Use GroupBox when the region is a labeled cluster of controls. Use Card when the region is a content surface with a heading and actions.

#Import

use gpui_kit::component::card::{
    Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,
};
// Installed in ui/components. That directory is on the Slint include path, so import by file name:
import { Card, CardFooter } from "card.slint";
use iced::widget::{column, container, mouse_area, row, scrollable, svg, text};
use iced::{Alignment, Element, Length, Task, Theme};

#Usage

#Default

Card::new()
    .child(
        CardHeader::new()
            .child(CardTitle::new().child("Team"))
            .child(CardDescription::new().child("Invite members and set their roles.")),
    )
    .child(CardContent::new().child("Twelve seats remaining on this workspace."))
    .child(
        CardFooter::new()
            .child(Button::new("cancel").label("Cancel"))
            .child(Button::new("invite").primary().label("Invite")),
    )
Card {
    title: "Team";
    description: "Invite members and set their roles.";
    Text {
        text: "Twelve seats remaining on this workspace.";
        color: Theme.muted-foreground;
    }
    CardFooter {
        Button {
            text: "Cancel";
        }
        Button {
            text: "Invite";
            variant: ButtonVariant.primary;
        }
    }
}
// The page's first documented sample and the Slint example's first
// `GroupBox`: a header, a body, and an end-aligned footer of actions.
let default = body_box(
    Card::new()
        .title("Team")
        .description("Invite members and set their roles.")
        .body(
            column![
                muted("Twelve seats remaining on this workspace."),
                card::footer(
                    row![
                        Button::new("Cancel")
                            .variant(Variant::Ghost)
                            .on_press(Message::Nothing),
                        Button::new("Invite")
                            .variant(Variant::Primary)
                            .on_press(Message::Saved),
                    ]
                    .spacing(theme::SPACING_SM),
                ),
            ]
            .spacing(theme::SPACING_LG),
        ),
);

#Stacked

h_flex()
    .gap_4()
    .items_start()
    .child(
        Card::new()
            .flex_1()
            .child(CardHeader::new().child(CardTitle::new().child("Usage")))
            .child(CardContent::new().child("4.2 GB of 10 GB used.")),
    )
    .child(
        Card::new()
            .flex_1()
            .child(CardHeader::new().child(CardTitle::new().child("Plan")))
            .child(CardContent::new().child("Team · billed monthly.")),
    )
HorizontalLayout {
    spacing: 16px;
    Card {
        title: "Usage";
        Text {
            text: "4.2 GB of 10 GB used.";
            color: Theme.muted-foreground;
        }
    }
    Card {
        title: "Plan";
        Text {
            text: "Team · billed monthly.";
            color: Theme.muted-foreground;
        }
    }
}
// Two cards side by side, each filling its half: the Slint example's
// `horizontal-stretch: 1` pair.
let stacked = body_box(
    row![
        Card::new()
            .title("Usage")
            .body(muted("4.2 GB of 10 GB used.")),
        Card::new()
            .title("Plan")
            .body(muted("Team · billed monthly.")),
    ]
    .spacing(theme::SPACING_LG),
);

#Login

GPUINo GPUI version of this example yet.
Card {
    title: "Login to your account";
    description: "Enter your email below to login to your account";
    Input {
        placeholder: "m@example.com";
    }
    Button {
        text: "Forgot your password?";
        variant: ButtonVariant.link;
        horizontal-stretch: 0;
    }
    Button {
        text: "Login";
        variant: ButtonVariant.primary;
        horizontal-stretch: 1;
    }
}
// A field and two actions in the card body: the Slint example's login form,
// with the input's placeholder and a link variant for the secondary action.
let login = body_box(
    Card::new()
        .title("Login to your account")
        .description("Enter your email below to login to your account")
        .body(
            column![
                Input::new("card-login-email")
                    .placeholder("m@example.com")
                    .value(app.email.clone())
                    .width(Length::Fill)
                    .on_input(Message::Email),
                Button::new("Forgot your password?")
                    .variant(Variant::Link)
                    .on_press(Message::Nothing),
                Button::new("Login")
                    .variant(Variant::Primary)
                    // .width(Length::Fill)
                    .on_press(Message::Saved),
            ]
            .spacing(theme::SPACING_MD),
        ),
);

#Size

GPUINo GPUI version of this example yet.
Card {
    title: "Default";
    description: "The standard inset and title size.";
}
Card {
    title: "Small";
    description: "A compact card for dense layouts.";
    size: ControlSize.small;
}
// The two sizes: medium keeps the 16px inset and the text-md title, small
// tightens both.
let size = body_box(
    column![
        Card::new()
            .title("Default")
            .description("The standard inset and title size."),
        Card::new()
            .title("Small")
            .description("A compact card for dense layouts.")
            .size(ControlSize::Small),
    ]
    .spacing(theme::SPACING_LG),
);

#Spacing

GPUINo GPUI version of this example yet.
Card {
    title: "16 px";
    description: "Inset and the gap between sections.";
    inset: 16px;
}
Card {
    title: "20 px";
    description: "Inset and the gap between sections.";
    inset: 20px;
}
Card {
    title: "24 px";
    description: "Inset and the gap between sections.";
    inset: 24px;
}
Card {
    title: "32 px";
    description: "Inset and the gap between sections.";
    inset: 32px;
}
// The inset is the card's padding and the gap between the header, the body
// and the footer at once. Slint lets the sample set it freely; this
// component derives it from `size`, so the sample shows the two it has —
// 16px at the medium size and 12px at the small size — rather than four it
// cannot.
let spacing = body_box(
    column![
        Card::new()
            .title("16 px")
            .description("The medium inset and the gap between sections.")
            .body(muted("A body at the same inset.")),
        Card::new()
            .title("12 px")
            .description("The small inset and the gap between sections.")
            .size(ControlSize::Small)
            .body(muted("A body at the same inset.")),
    ]
    .spacing(theme::SPACING_LG),
);

#Image

GPUINo GPUI version of this example yet.
Card {
    cover: @image-url("assets/attachment-preview.svg");
    badge: "Featured";
    title: "Design systems meetup";
    description: "A practical talk on component APIs and shipping faster.";
}
// Slint's card has a `cover` image slot above the header. This one draws
// the header first and has no slot above it, so the caller's media element
// is the body — an application passes `image(handle)` or `svg(handle)`
// here — and the badge rides above the title as it does in Slint.
let image = body_box(
    Card::new()
        .badge("Featured")
        .title("Design systems meetup")
        .description("A practical talk on component APIs and shipping faster.")
        .body(cover("attachment-preview.svg")),
);

#Parts

  • Card — bordered surface, padding, and vertical gap.
  • CardHeader — title and description stack.
  • CardTitle — primary heading.
  • CardDescription — supporting copy in the muted foreground.
  • CardContent — main body.
  • CardFooter — action row.

All parts implement [ParentElement] and [Styled], so you can add children and refine layout without a sealed API.

#Copy source

This is a GPUI-target registry component. The CLI copies crates/component/src/card.rs. There is no gpui-base Card primitive; keep gpui-base as the dependency for behavior used by other copied components.