Skip to content
IzzyBlazor 0.1.0-alpha.17

Enter all results ↑ ↓ move Esc close

Card

Media, header, body sections, actions and footer.

JavaScript off: what the HTML and CSS do on their own.

Usage

razor
<Card Title="Website" Description="Updated 2 hours ago">
    <TextBlock>Marketing pages and the docs, deployed from main.</TextBlock>
    <CardFooter Justify="Justify.End">
        <Button Href="projects/website" Size="ControlSize.Sm">Open</Button>
    </CardFooter>
</Card>

@* The whole card is a link, through its title. *@
<Card Title="Course app" Description="12 lessons" Href="courses/app" />

A card frames one thing people look at or act on, such as a project, a person or a plan, and often sits in a grid with others like it. For a band across the page, such as a hero, use a Section.

Variants

Use Variant for Outline (the default), Elevated or Filled, and Title and Description for the header.

Preview

Website

Updated 2 hours ago

Marketing pages and the docs, deployed from main.

Mobile app

Updated yesterday

The iOS and Android apps, released every two weeks.

Billing API

Updated Oct 12

Invoices, payments and refunds for every plan.

Code

Variants.razor
<Row Gap="4" Wrap Align="Align.Stretch" Grow>
    <Card Title="Website" Description="Updated 2 hours ago" Grow Basis="14.Rem">
        <TextBlock Color="Color.Muted">Marketing pages and the docs, deployed from main.</TextBlock>
    </Card>
    <Card Variant="CardVariant.Elevated" Title="Mobile app" Description="Updated yesterday" Grow Basis="14.Rem">
        <TextBlock Color="Color.Muted">The iOS and Android apps, released every two weeks.</TextBlock>
    </Card>
    <Card Variant="CardVariant.Filled" Title="Billing API" Description="Updated Oct 12" Grow Basis="14.Rem">
        <TextBlock Color="Color.Muted">Invoices, payments and refunds for every plan.</TextBlock>
    </Card>
</Row>

The title is an h3; set TitleLevel to fit the page's outline. Content between the tags is the body.

Sizes

Use Size for the padding and the title's size (Md by default): Sm for dense grids, Lg for a card on its own.

Preview

Small

For dense grids and side panels

Less padding and a smaller title.

Medium

The default

The padding and title most cards use.

Large

For a card on its own

More room around everything.

Code

Sizes.razor
<Row Gap="4" Wrap Align="Align.Start" Grow>
    <Card Size="ControlSize.Sm" Title="Small" Description="For dense grids and side panels" Grow Basis="12.Rem">
        <TextBlock Color="Color.Muted">Less padding and a smaller title.</TextBlock>
    </Card>
    <Card Title="Medium" Description="The default" Grow Basis="12.Rem">
        <TextBlock Color="Color.Muted">The padding and title most cards use.</TextBlock>
    </Card>
    <Card Size="ControlSize.Lg" Title="Large" Description="For a card on its own" Grow Basis="12.Rem">
        <TextBlock Color="Color.Muted">More room around everything.</TextBlock>
    </Card>
</Row>

A linked card

Use Href to make the whole card a link through its title; buttons in CardActions, CardFooter and the body stay clickable on their own.

Preview

Website

izzyblazor.dev

Deployed Blazor

Course app

Lessons and progress

12 lessons · 3 hours

Code

Linked.razor
@inject NavigationManager Navigation

<Row Gap="4" Wrap Align="Align.Stretch" Grow>
    <Card Title="Website" Description="izzyblazor.dev" Href="@Navigation.GetUriWithQueryParameter("project", "website")" Grow Basis="16.Rem">
        <CardActions>
            <IconButton Icon="Lucide.Star" Label="Add to favorites" Variant="ButtonVariant.Ghost" Accent="Accent.Neutral" Size="ControlSize.Sm" />
        </CardActions>
        <Row Gap="2" Wrap>
            <Badge Status="Status.Success" Dot>Deployed</Badge>
            <Badge>Blazor</Badge>
        </Row>
        <CardFooter>
            <TextBlock As="span" Size="TextSize.Xs" Color="Color.Muted">3 contributors</TextBlock>
        </CardFooter>
    </Card>
    <Card Variant="CardVariant.Elevated" Title="Course app" Description="Lessons and progress" Href="@Navigation.GetUriWithQueryParameter("project", "course")" Grow Basis="16.Rem">
        <CardMedia>
            <svg viewBox="0 0 400 140" preserveAspectRatio="xMidYMid slice" style="height: 8rem" aria-hidden="true">
                <defs><linearGradient id="course-cover" x1="0" x2="1"><stop offset="0" stop-color="#4f46e5" /><stop offset="1" stop-color="#0ea5e9" /></linearGradient></defs>
                <rect width="400" height="140" fill="url(#course-cover)" />
            </svg>
        </CardMedia>
        <TextBlock Color="Color.Muted">12 lessons · 3 hours</TextBlock>
    </Card>
</Row>

The link is the title, so a linked card needs a Title. CardMedia draws an image, video or SVG across the top, edge to edge; one that only decorates gets alt="" on an img or aria-hidden="true" on an svg. On a linked card an image is part of the link, and a video's controls stay usable above it.

Your own heading

Use CardHeader for content above the title, such as who wrote it, and CardActions for buttons or a Menu at the end of the heading row.

Preview

Maya Reyes 2 hours ago

The Q3 roadmap is ready

Three themes this quarter: faster builds, a calmer on-call week and the new billing pages. Comments are open until Friday.

Code

Post.razor
<Card Title="The Q3 roadmap is ready" Grow MaxWidth="28.Rem">
    <CardHeader>
        <Row Gap="2" Align="Align.Center">
            <Avatar Name="Maya Reyes" Size="AvatarSize.Sm" Decorative />
            <TextBlock As="span" Size="TextSize.Sm" Weight="FontWeight.Medium">Maya Reyes</TextBlock>
            <TextBlock As="span" Size="TextSize.Sm" Color="Color.Muted">2 hours ago</TextBlock>
        </Row>
    </CardHeader>
    <CardActions>
        <Menu Placement="Placement.BottomEnd">
            <Trigger>
                <IconButton Icon="Lucide.Ellipsis" Label="Post actions" Variant="ButtonVariant.Ghost" Accent="Accent.Neutral" Size="ControlSize.Sm" @attributes="context" />
            </Trigger>
            <ChildContent>
                <MenuItem Icon="Lucide.Pin">Pin to the top</MenuItem>
                <MenuItem Icon="Lucide.Link">Copy link</MenuItem>
            </ChildContent>
        </Menu>
    </CardActions>
    <TextBlock>Three themes this quarter: faster builds, a calmer on-call week and the new billing pages. Comments are open until Friday.</TextBlock>
    <CardFooter>
        <Button Variant="ButtonVariant.Outline" Accent="Accent.Neutral" Size="ControlSize.Sm" StartIcon="Lucide.MessageCircle">Comment</Button>
        <Button Variant="ButtonVariant.Outline" Accent="Accent.Neutral" Size="ControlSize.Sm" StartIcon="Lucide.Share2">Share</Button>
    </CardFooter>
</Card>

Use CardBody for the body's layout, with Gap, Align and Direction, and CardFooter for a row of buttons at the bottom, with Justify.

Write the footer last, directly in the card: inside a CardBody or a form it's an ordinary row, not the card's footer.

Flush content

Use Flush on a CardBody to run a Table or a divided list to the card's edges.

Preview

Recent orders

Last 7 days

Recent orders
OrderCustomerTotal
#1042Ada Lovelace$128
#1041Grace Hopper$64
#1040Alan Turing$312

Code

Flush.razor
<Card Title="Recent orders" Description="Last 7 days" Grow MaxWidth="32.Rem">
    <CardActions>
        <Button Href="docs/components/table" Variant="ButtonVariant.Ghost" Size="ControlSize.Sm">View all</Button>
    </CardActions>
    <CardBody Flush>
        <Table Items="RecentOrders" Caption="Recent orders" QueryPrefix="recent-">
            <PropertyColumn Property="o => o.Number" Title="Order" />
            <PropertyColumn Property="o => o.Customer" />
            <PropertyColumn Property="o => o.Total" Format="C0" />
        </Table>
    </CardBody>
</Card>

Use Dividers to make each CardBody a section with a line between, and give the CardFooter a Bg or BorderTop to make it a bar across the bottom.

Preview

Pro plan

Renews March 3, 2027

$240 / year

12 of 20 seats in use, billed to the card ending 4242.

Storage
Build minutes

Code

Sections.razor
<Card Dividers Title="Pro plan" Description="Renews March 3, 2027" Grow MaxWidth="30.Rem">
    <CardBody Gap="1">
        <TextBlock Size="TextSize.Xl2" Weight="FontWeight.Semibold">$240 / year</TextBlock>
        <TextBlock Size="TextSize.Sm" Color="Color.Muted">12 of 20 seats in use, billed to the card ending 4242.</TextBlock>
    </CardBody>
    <CardBody Gap="3">
        <Progress Value="64" Label="Storage" ValueText="64 of 100 GB" Size="ControlSize.Sm" />
        <Progress Value="38" Label="Build minutes" ValueText="1,140 of 3,000" Size="ControlSize.Sm" />
    </CardBody>
    <CardFooter Justify="Justify.End" Bg="Color.FillSubtle" BorderTop="1">
        <Button Variant="ButtonVariant.Ghost" Accent="Accent.Neutral" Size="ControlSize.Sm">Cancel plan</Button>
        <Button Size="ControlSize.Sm">Upgrade</Button>
    </CardFooter>
</Card>

A form in a card

Put the Form around the card, so the submit button in its CardFooter is inside the form.

Preview

Workspace

Seen by everyone in it

Every Monday, to each member.

Code

Settings.razor
@using System.ComponentModel.DataAnnotations

@* The Form goes around the Card, so the Save button in the CardFooter is inside the form. *@
<Form Model="Model" FormName="card-settings" OnValidSubmit="Save" Grow MaxWidth="32.Rem">
    <Card Dividers Title="Workspace" Description="Seen by everyone in it">
        <CardBody>
            <TextField @bind-Value="Model!.Name" />
        </CardBody>
        <CardBody>
            <Switch @bind-Value="Model!.WeeklySummary" ControlPosition="ControlPosition.End" Hint="Every Monday, to each member." />
        </CardBody>
        <CardFooter Justify="Justify.End" Bg="Color.FillSubtle" BorderTop="1">
            <Button Submit Size="ControlSize.Sm">Save</Button>
        </CardFooter>
    </Card>
    @if (_saved is not null)
    {
        <Alert Status="Status.Success">Saved: @_saved</Alert>
    }
</Form>

@code {
    [SupplyParameterFromForm(FormName = "card-settings")]
    private WorkspaceSettings? Model { get; set; }

    private string? _saved;

    protected override void OnInitialized() => Model ??= new();

    // What was saved, not the fields: on an interactive page they change as people type.
    private void Save() => _saved = $"{Model!.Name}, {(Model.WeeklySummary ? "with" : "without")} the weekly summary.";
}

Off: Save posts the form and the page loads again, with the result under the card.

A CardFooter inside a form isn't drawn as the card's footer, and CardMedia, CardHeader and CardActions are drawn at the card's level, so a form inside the card reaches none of them. A form whose buttons are in its own FormActions can go inside.

Cards in a grid

Use a Grid with MinChildWidth to fit as many cards to a row as there's room for. Cards in a row take its height, and a CardFooter stays at the bottom.

Preview

Website

Updated 2 hours ago

Marketing pages and the docs.

Mobile app

Updated yesterday

The iOS and Android apps, released every two weeks from the release branch.

Billing API

Updated Oct 12

Invoices, payments and refunds.

Code

Projects.razor
<Grid MinChildWidth="14.Rem" Gap="4" Grow>
    @foreach (var project in AllProjects)
    {
        <Card Title="@project.Name" Description="@project.Updated" Size="ControlSize.Sm">
            <TextBlock Color="Color.Muted">@project.Summary</TextBlock>
            <CardFooter>
                <Badge Status="@project.Status" Dot>@project.StatusText</Badge>
            </CardFooter>
        </Card>
    }
</Grid>

Put the body's text in an element, such as a TextBlock: above a footer, bare text gets the body's gap, not a section's space.

Parts

Written inside Card. The API page lists their parameters.

  • CardMedia: an image or video at the top of a Card, edge to edge.
  • CardHeader: custom heading content in a Card, above its Title, such as an avatar row, beside any CardActions.
  • CardActions: buttons at the end of a Card's heading row, such as a menu.
  • CardBody: a Card's body with layout props: a column with Gap, Align, Justify and Direction, like Stack.
  • CardFooter: the bottom row of a Card, such as buttons or metadata.

Mode support

What works in each mode, Card
FeatureOffStaticLive
EverythingYesYesYes

Off: JavaScript disabled. Static: a static SSR page with IzzyBlazor's script. Live: an interactive page (Server, WebAssembly or Auto).

Accessibility

  • Title renders a real heading, h3 by default; set TitleLevel to fit the page's outline.
  • With Href, the link is the title alone, so its name is the Title text, not the whole card. It needs a Title, and throws without one.
  • Keyboard users tab to the title link, and the focus ring is drawn around the whole card.
  • Links and buttons in the header, CardActions, CardFooter and the body stay their own tab stops and stay clickable above the stretched link.
  • CardFooter is drawn last but stays where it's written in the DOM, which is the order screen readers follow, so write it last.

Parameters

Card parameters
PropTypeDefault
TitleThe heading.string?
TitleLevelThe heading level of Title, 1 to 6.int3
DescriptionA muted line under the title.string?
ChildContentThe card's content: the body, plus any CardMedia, CardHeader, CardActions, CardBody and CardFooter.RenderFragment?
HrefMakes the whole card a link to this address, through its title.string?
DividersMakes each CardBody a section, with a line across the card between sections, as in a settings page.boolfalse
VariantHow it's drawn.CardVariantCardVariant.Outline
SizeInner spacing and title size.ControlSizeControlSize.Md

See also