suiss/ui
  • Docs
  • Components
  • Patterns
  • Interactions
  • Charts
  • Blocks
GitHubGet started
DocsComponentsPatternsInteractionsChartsBlocks
Components82
  • Overview
  • Accordion
  • Alert
  • Alert Dialog
  • Alert Sheet
  • Amount Input
  • Aspect Ratio
  • Attachment
  • Avatar
  • Badge
  • Bottom Bar
  • Breadcrumb
  • Bubble
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Chip
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Dialog
  • Direction
  • Drawer
  • Dropdown Menu
  • Empty
  • FAB
  • Field
  • Fullscreen Menu
  • Haptics
  • Header
  • Hover Card
  • Icon
  • Input
  • Input Accessory Bar
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Keyboard Scroll View
  • Keypad
  • Label
  • Marker
  • Menubar
  • Message
  • Message Scroller
  • Native Select
  • Navigation Menu
  • Page Indicator
  • Pagination
  • Pin Pad
  • Popover
  • Progress
  • QR Scanner
  • Questionnaire
  • Radio Group
  • Resizable
  • Screen
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Signature Pad
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Tab Bar
  • Table
  • Tabs
  • Text
  • Textarea
  • Toast
  • Toggle
  • Toggle Group
  • Tooltip
  • Wheel Picker
  • Widget

suiss/ui · Component

Card

A raised container that groups related content and actions about a single subject.

Card is an open-source component from suiss/ui for React, React Native, SwiftUI and Jetpack Compose. The @esuiss/ui CLI copies its source into your project, so you own the code.

import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

export default function CardDemo() {
  return (
    <div className="w-96">
      <Card>
        <CardHeader>
          <CardTitle>Team plan</CardTitle>
          <CardDescription>
            Everything your team needs to ship faster.
          </CardDescription>
        </CardHeader>
        <CardContent>
          <p>
            Unlimited projects, shared workspaces, and priority support for up
            to ten members.
          </p>
        </CardContent>
        <CardFooter className="gap-2">
          <Button>Upgrade</Button>
          <Button variant="ghost">Learn more</Button>
        </CardFooter>
      </Card>
    </div>
  )
}

A Card stacks a CardHeader, CardContent and CardFooter with consistent spacing. The header holds a CardTitle, an optional CardDescription, and an optional CardAction that sits in the top-end corner. An img placed as the first child runs edge to edge, with rounded top corners and no top padding.

Use a card to present a self-contained summary, such as a plan, a metric or a place. For dense lists of rows, use Item or a table instead.

Installation

CLI

$ pnpm dlx shadcn@latest add @esuiss/card

Works in any project with a components.json. See Installation for the one-line registry setup. Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add card

Manual

  1. Add the cn helper.

  2. Copy the source into components/ui/card.tsx:

components/ui/card.tsx
import type * as React from "react"
import { cn } from "@/lib/utils"

function Card({
  className,
  size = "default",
  variant = "default",
  ...props
}: React.ComponentProps<"div"> & {
  size?: "default" | "sm"
  variant?: "default" | "filled"
}) {
  return (
    <div
      data-slot="card"
      data-size={size}
  1. Update the import paths to match your project.

Usage

import { Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter } from "@/components/ui/card"
<div className="w-96">
  <Card>
    <CardHeader>
      <CardTitle>Team plan</CardTitle>
      <CardDescription>
        Everything your team needs to ship faster.
      </CardDescription>
    </CardHeader>
    <CardContent>
      <p>
        Unlimited projects, shared workspaces, and priority support for up to
        ten members.
      </p>
    </CardContent>
    <CardFooter className="gap-2">
      <Button>Upgrade</Button>
      <Button variant="ghost">Learn more</Button>
    </CardFooter>
  </Card>
</div>

All parts are plain div elements, so you can omit any of them. Add border-b to CardHeader or border-t to CardFooter to draw a divider; the card adds matching padding on that side.

Spacing is controlled by the --card-spacing custom property, which the sm size reduces.

Examples

Small

The sm size with tighter spacing.

import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

export default function CardSmall() {
  return (
    <div className="w-96">
      <Card size="sm">
        <CardHeader>
          <CardTitle>Team plan</CardTitle>
          <CardDescription>
            Everything your team needs to ship faster.
          </CardDescription>
        </CardHeader>
        <CardContent>
          <p>
            Unlimited projects, shared workspaces, and priority support for up
            to ten members.
          </p>
        </CardContent>
        <CardFooter className="gap-2">
          <Button>Upgrade</Button>
          <Button variant="ghost">Learn more</Button>
        </CardFooter>
      </Card>
    </div>
  )
}

Filled

The filled variant on a flat bg-muted surface with no shadow.

import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

export default function CardFilled() {
  return (
    <div className="w-96">
      <Card variant="filled">
        <CardHeader>
          <CardTitle>Team plan</CardTitle>
          <CardDescription>
            Everything your team needs to ship faster.
          </CardDescription>
        </CardHeader>
        <CardContent>
          <p>
            Unlimited projects, shared workspaces, and priority support for up
            to ten members.
          </p>
        </CardContent>
        <CardFooter className="gap-2">
          <Button>Upgrade</Button>
          <Button variant="ghost">Learn more</Button>
        </CardFooter>
      </Card>
    </div>
  )
}

With action

Places an icon button in the header with CardAction.

import { DotsThreeIcon } from "@phosphor-icons/react"
import { Button } from "@/components/ui/button"
import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

export default function CardWithAction() {
  return (
    <div className="w-96">
      <Card>
        <CardHeader>
          <CardTitle>Monthly revenue</CardTitle>
          <CardDescription>Updated five minutes ago</CardDescription>
          <CardAction>
            <Button variant="ghost" size="icon-sm" aria-label="More options">
              <DotsThreeIcon />
            </Button>
          </CardAction>
        </CardHeader>
        <CardContent>
          <p className="text-3xl font-semibold tabular-nums">$24,380</p>
        </CardContent>
      </Card>
    </div>
  )
}

With borders

Separates the header and footer with borders.

import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

export default function CardWithBorders() {
  return (
    <div className="w-96">
      <Card>
        <CardHeader className="border-b">
          <CardTitle>Notifications</CardTitle>
          <CardDescription>Choose what you want to hear about.</CardDescription>
        </CardHeader>
        <CardContent>
          <p>You have three unread messages and one pending invite.</p>
        </CardContent>
        <CardFooter className="justify-end border-t">
          <Button variant="outline">Mark all as read</Button>
        </CardFooter>
      </Card>
    </div>
  )
}

With image

Leads with a full-bleed image above the header.

import { Button } from "@/components/ui/button"
import {
  Card,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

export default function CardWithImage() {
  return (
    <div className="w-96">
      <Card>
        <img
          src="https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=800&q=80"
          alt="Mountain lake at sunrise"
          className="aspect-video w-full object-cover"
        />
        <CardHeader>
          <CardTitle>Lake Louise</CardTitle>
          <CardDescription>Banff National Park, Canada</CardDescription>
        </CardHeader>
        <CardFooter>
          <Button className="w-full">Book a stay</Button>
        </CardFooter>
      </Card>
    </div>
  )
}

API Reference

Anatomy7 parts
<Card>
<CardHeader>
<CardTitle />
<CardDescription />
<CardAction />
</CardHeader>
<CardContent />
<CardFooter />
</Card>
01

Card

The rounded container that sets size and surface.

PropTypeDefaultDescription
size"default" | "sm""default"Inner spacing: default or the more compact sm.
variant"default" | "filled""default"Surface style. default is raised with a shadow and ring; filled uses bg-muted with no shadow.

Also accepts the props of React.ComponentProps<"div">.

02

CardHeader

Lays out the title, description and action.

Also accepts the props of React.ComponentProps<"div">.

03

CardTitle

The card's heading text.

Also accepts the props of React.ComponentProps<"div">.

04

CardDescription

Secondary text below the title.

Also accepts the props of React.ComponentProps<"div">.

05

CardAction

A slot in the header's top-end corner for a button or menu.

Also accepts the props of React.ComponentProps<"div">.

06

CardContent

The main body, padded to the card's spacing.

Also accepts the props of React.ComponentProps<"div">.

07

CardFooter

A row for actions or summary at the bottom.

Also accepts the props of React.ComponentProps<"div">.

Accessibility

  • Card parts are non-semantic div elements, and CardTitle is not a heading. If the title belongs in the document outline, place a heading element such as h2 inside it.
  • Icon-only buttons in CardAction need an aria-label.

Notes

  • Styling hooks: data-slot values card, card-header, card-title, card-description, card-action, card-content and card-footer, plus data-size and data-variant on the root.
  • The header switches to a two-column grid when it contains a CardAction.
  • Calendar detects data-slot="card-content" and drops its own background inside a card.
iOS
Card on iOS in light modeCard on iOS in dark mode
import { View } from "react-native"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Text } from "@/components/ui/text"

export function CardExample() {
  return (
    <Card className="w-full">
      <CardHeader>
        <CardTitle>Login to your account</CardTitle>
        <CardDescription>
          Enter your email below to login to your account
        </CardDescription>
      </CardHeader>
      <CardContent className="gap-4">
        <View className="gap-2">
          <Label nativeID="card-email">Email</Label>
          <Input
            accessibilityLabelledBy="card-email"
            placeholder="[email protected]"
            keyboardType="email-address"
            autoCapitalize="none"
          />
        </View>
        <View className="gap-2">
          <Label nativeID="card-password">Password</Label>
          <Input accessibilityLabelledBy="card-password" secureTextEntry />
        </View>
      </CardContent>
      <CardFooter className="flex-col gap-2">
        <Button block>
          <Text>Login</Text>
        </Button>
        <Button block variant="secondary">
          <Text>Login with Google</Text>
        </Button>
      </CardFooter>
    </Card>
  )
}
import EsuissUI
import SwiftUI

struct CardLoginDemo: View {
  @State private var email = ""
  @State private var password = ""

  var body: some View {
    Card {
      CardHeader("Login to your account", description: "Enter your email below to login to your account")
      CardContent {
        VStack(alignment: .leading, spacing: 16) {
          VStack(alignment: .leading, spacing: 8) {
            FieldLabel("Email")
              .accessibilityHidden(true)
            TextField("Email", text: $email, prompt: Text(verbatim: "[email protected]"))
              .textFieldStyle(.input)
              .keyboardType(.emailAddress)
              .textInputAutocapitalization(.never)
          }
          VStack(alignment: .leading, spacing: 8) {
            FieldLabel("Password")
              .accessibilityHidden(true)
            SecureField("Password", text: $password, prompt: Text(verbatim: ""))
              .textFieldStyle(.input)
          }
        }
      }
      CardFooter {
        VStack(spacing: 8) {
          Button("Login") {}
            .buttonStyle(.variant(block: true))
          Button("Login with Google") {}
            .buttonStyle(.variant(.secondary, block: true))
        }
      }
    }
  }
}
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonVariant
import com.esuiss.ui.components.Card
import com.esuiss.ui.components.CardContent
import com.esuiss.ui.components.CardFooter
import com.esuiss.ui.components.CardHeader
import com.esuiss.ui.components.Input
import com.esuiss.ui.components.Label

@Composable
fun CardDefault() {
    var email by rememberSaveable { mutableStateOf("") }
    var password by rememberSaveable { mutableStateOf("") }
    Card {
        CardHeader("Login to your account", description = "Enter your email below to login to your account")
        CardContent {
            Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
                Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
                    Label("Email")
                    Input(
                        email,
                        { email = it },
                        Modifier.semantics { contentDescription = "Email" },
                        placeholder = "[email protected]",
                        keyboardOptions = KeyboardOptions(
                            capitalization = KeyboardCapitalization.None,
                            autoCorrectEnabled = false,
                            keyboardType = KeyboardType.Email,
                        ),
                    )
                }
                Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
                    Label("Password")
                    Input(
                        password,
                        { password = it },
                        Modifier.semantics { contentDescription = "Password" },
                        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
                        visualTransformation = PasswordVisualTransformation(),
                    )
                }
            }
        }
        CardFooter {
            Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
                Button("Login", onClick = {}, block = true)
                Button("Login with Google", onClick = {}, variant = ButtonVariant.Secondary, block = true)
            }
        }
    }
}

Card is a rounded View that stacks a CardHeader, CardContent and CardFooter with consistent spacing. The default variant is a raised surface with a hairline border and a shadow; the filled variant is a flat bg-muted surface.

The sm size reduces the padding and gaps for dense layouts. All parts read the size from context, so their horizontal padding stays aligned.

Installation

$ pnpm dlx shadcn@latest add @esuiss-rn/card

See Installation for the components.json setup. Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add card
$ pnpm dlx @esuiss/ui@latest add card --platform swift
$ pnpm dlx @esuiss/ui@latest add card --platform kotlin

The CLI also adds what it builds on: Text.

Manual

  1. Install the dependencies:
npx expo install cn
  1. Add the pieces it builds on: Text.

  2. Copy the source into components/ui/card.tsx:

uniwind/components/ui/card.tsx
import { cn } from "cn"
import * as React from "react"
import { View } from "react-native"
import { Text, TextClassContext } from "@/components/ui/text"

type CardSize = "default" | "sm"

type CardVariant = "default" | "filled"

const CardSizeContext = React.createContext<CardSize>("default")

const SPACING_X: Record<CardSize, string> = { default: "px-6", sm: "px-4" }

function Card({
  className,
  size = "default",
  1. Update the import paths to match your project.
  1. Install the dependencies:
npx expo install cn
  1. Add the pieces it builds on: Text.

  2. Copy the source into components/ui/card.tsx:

nativewind/components/ui/card.tsx
import { cn } from "cn"
import * as React from "react"
import { View } from "react-native"
import { Text, TextClassContext } from "@/components/ui/text"

type CardSize = "default" | "sm"

type CardVariant = "default" | "filled"

const CardSizeContext = React.createContext<CardSize>("default")

const SPACING_X: Record<CardSize, string> = { default: "px-6", sm: "px-4" }

function Card({
  className,
  size = "default",
  1. Update the import paths to match your project.
  1. Add the pieces it builds on: Text.

  2. Copy the source into components/ui/card.tsx:

stylesheet/components/ui/card.tsx
import * as React from "react"
import { StyleSheet, View } from "react-native"
import { Text, TextStyleContext } from "@/components/ui/text"
import { fontSize, radius, shadow, useTheme } from "@/components/ui/theme"

type CardSize = "default" | "sm"

type CardVariant = "default" | "filled"

const CardSizeContext = React.createContext<CardSize>("default")

const SPACING: Record<CardSize, number> = { default: 24, sm: 16 }

const styles = StyleSheet.create({
  card: {
    flexDirection: "column",
  1. Update the import paths to match your project.
  1. Target iOS 17 or later.

  2. Copy the theme files every view uses:

swift/Theme/Theme.swift
import SwiftUI
import UIKit

struct RGBA: Sendable {
  let red: CGFloat
  let green: CGFloat
  let blue: CGFloat
  let alpha: CGFloat

  init(_ hex: UInt32, _ alpha: CGFloat = 1) {
    red = CGFloat((hex >> 16) & 0xFF) / 255
    green = CGFloat((hex >> 8) & 0xFF) / 255
    blue = CGFloat(hex & 0xFF) / 255
    self.alpha = alpha
  }
swift/Theme/Tokens.swift
import SwiftUI

public enum EsuissColor {
  public static let background = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))
  public static let foreground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let card = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
  public static let cardForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let popover = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
  public static let popoverForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let primary = Color.adaptive(light: .init(0x0071E3), dark: .init(0x2997FF))
  public static let primaryForeground = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0xFFFFFF))
  public static let secondary = Color.adaptive(light: .init(0x000000, 0.04), dark: .init(0xFFFFFF, 0.1))
  public static let secondaryForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let muted = Color.adaptive(light: .init(0xF5F5F7), dark: .init(0x161617))
  public static let mutedForeground = Color.adaptive(light: .init(0x6E6E73), dark: .init(0xA1A1A6))
  public static let accent = Color.adaptive(light: .init(0x000000, 0.06), dark: .init(0xFFFFFF, 0.1))
  1. Copy the source:
swift/Components/Card.swift
import SwiftUI

public struct Card<Content: View>: View {
  public enum Variant: CaseIterable, Sendable {
    case `default`, filled
  }

  public enum Size: CaseIterable, Sendable {
    case `default`, sm

    var spacing: CGFloat {
      switch self {
      case .default: 24
      case .sm: 16
      }
    }
  1. Add the files to your app target.
  1. Set minSdk to 24 or higher and add the dependencies:
build.gradle.kts
implementation(platform("androidx.compose:compose-bom:2026.09.00"))
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.ui:ui")
  1. Copy the theme files every view uses:
kotlin/theme/Theme.kt
package com.esuiss.ui.theme

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.dropShadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.shadow.Shadow as DropShadow
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
kotlin/theme/Tokens.kt
package com.esuiss.ui.theme

import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

@Immutable
data class Colors(
    val background: Color,
    val foreground: Color,
    val card: Color,
    val cardForeground: Color,
    val popover: Color,
    val popoverForeground: Color,
    val primary: Color,
    val primaryForeground: Color,
  1. Copy the source and every file it uses:
kotlin/components/Card.kt
package com.esuiss.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
kotlin/components/Content.kt
package com.esuiss.ui.components

import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

val LocalContentStyle = compositionLocalOf { TextStyle.Default }

val LocalContentColor = compositionLocalOf { Color.Unspecified }

val LocalIconSize = compositionLocalOf { 20.dp }
  1. Update the com.esuiss.ui package names to match your project.

Usage

import { Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter } from "@/components/ui/card"
<Card className="w-full">
  <CardHeader>
    <CardTitle>Login to your account</CardTitle>
    <CardDescription>
      Enter your email below to login to your account
    </CardDescription>
  </CardHeader>
  <CardContent className="gap-4">
    <View className="gap-2">
      <Label nativeID="card-email">Email</Label>
      <Input
        accessibilityLabelledBy="card-email"
        placeholder="[email protected]"
        keyboardType="email-address"
        autoCapitalize="none"
      />
    </View>
    <View className="gap-2">
      <Label nativeID="card-password">Password</Label>
      <Input accessibilityLabelledBy="card-password" secureTextEntry />
    </View>
  </CardContent>
  <CardFooter className="flex-col gap-2">
    <Button block>
      <Text>Login</Text>
    </Button>
    <Button block variant="secondary">
      <Text>Login with Google</Text>
    </Button>
  </CardFooter>
</Card>

Put a CardTitle and CardDescription in CardHeader. Add a CardAction anywhere in the header and it is placed in the top-end corner beside the title.

To show an image edge to edge, place it directly in the card before the header and add pt-0 to the card; only the header, content and footer are padded horizontally.

Examples

Small

The compact sm size.

iOS
Card (small) on iOS in light modeCard (small) on iOS in dark mode
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Text } from "@/components/ui/text"

export function CardSmallExample() {
  return (
    <Card size="sm" className="w-full">
      <CardHeader>
        <CardTitle>Team plan</CardTitle>
        <CardDescription>Up to 10 members</CardDescription>
      </CardHeader>
      <CardContent>
        <Text className="text-sm">
          Shared workspaces, unlimited projects and priority support.
        </Text>
      </CardContent>
    </Card>
  )
}
import EsuissUI
import SwiftUI

struct CardSmallExample: View {
  var body: some View {
    Card(size: .sm) {
      CardHeader("Team plan", description: "Up to 10 members")
      CardContent {
        Text("Shared workspaces, unlimited projects and priority support.")
          .textStyle(.sm)
      }
    }
  }
}
import androidx.compose.runtime.Composable
import com.esuiss.ui.components.Card
import com.esuiss.ui.components.CardContent
import com.esuiss.ui.components.CardHeader
import com.esuiss.ui.components.CardSize
import com.esuiss.ui.components.Text
import com.esuiss.ui.theme.Typography

@Composable
fun CardSmall() {
    Card(size = CardSize.Sm) {
        CardHeader("Team plan", description = "Up to 10 members")
        CardContent {
            Text("Shared workspaces, unlimited projects and priority support.", style = Typography.sm)
        }
    }
}

With action

A CardAction button beside the title.

iOS
Card (with action) on iOS in light modeCard (with action) on iOS in dark mode
import { BellIcon } from "phosphor-react-native"
import { Button } from "@/components/ui/button"
import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Icon } from "@/components/ui/icon"
import { Text } from "@/components/ui/text"

export function CardWithActionExample() {
  return (
    <Card className="w-full">
      <CardHeader>
        <CardTitle>Notifications</CardTitle>
        <CardDescription>You have 3 unread messages.</CardDescription>
        <CardAction>
          <Button size="icon" variant="ghost">
            <Icon as={BellIcon} />
          </Button>
        </CardAction>
      </CardHeader>
      <CardContent>
        <Text className="text-sm">
          Your call has been confirmed for tomorrow at 10:00.
        </Text>
      </CardContent>
      <CardFooter>
        <Button block>
          <Text>Mark all as read</Text>
        </Button>
      </CardFooter>
    </Card>
  )
}
import EsuissUI
import PhosphorSwift
import SwiftUI

struct CardWithActionExample: View {
  var body: some View {
    Card {
      CardHeader("Notifications", description: "You have 3 unread messages.") {
        Button {} label: {
          Ph.bell.regular
            .frame(width: VariantButtonStyle.Size.icon.iconSize, height: VariantButtonStyle.Size.icon.iconSize)
        }
        .buttonStyle(.variant(.ghost, size: .icon))
        .accessibilityLabel(Text("Notifications"))
      }
      CardContent {
        Text("Your call has been confirmed for tomorrow at 10:00.")
          .textStyle(.sm)
      }
      CardFooter {
        Button("Mark all as read") {}
          .buttonStyle(.variant(block: true))
      }
    }
  }
}
import androidx.compose.runtime.Composable
import androidx.compose.ui.semantics.contentDescription
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.Bell
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonSize
import com.esuiss.ui.components.ButtonVariant
import com.esuiss.ui.components.Card
import com.esuiss.ui.components.CardContent
import com.esuiss.ui.components.CardFooter
import com.esuiss.ui.components.CardHeader
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Text
import com.esuiss.ui.theme.Typography

@Composable
fun CardWithAction() {
    Card {
        CardHeader(
            "Notifications",
            description = "You have 3 unread messages.",
            action = {
                Button(onClick = {}, variant = ButtonVariant.Ghost, size = ButtonSize.Icon) {
                    Icon(PhosphorIcons.Regular.Bell, contentDescription = "Notifications")
                }
            },
        )
        CardContent {
            Text("Your call has been confirmed for tomorrow at 10:00.", style = Typography.sm)
        }
        CardFooter {
            Button("Mark all as read", onClick = {}, block = true)
        }
    }
}

With image

An edge-to-edge image above the header.

iOS
Card (with image) on iOS in light modeCard (with image) on iOS in dark mode
import { Image } from "react-native"
import { Button } from "@/components/ui/button"
import { Card, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Text } from "@/components/ui/text"

export function CardWithImageExample() {
  return (
    <Card className="w-full pt-0">
      <Image
        source={{
          uri: "https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80",
        }}
        className="aspect-video w-full"
      />
      <CardHeader>
        <CardTitle>Mountain retreat</CardTitle>
        <CardDescription>A quiet cabin with a view.</CardDescription>
      </CardHeader>
      <CardFooter>
        <Button block variant="tinted">
          <Text>Book now</Text>
        </Button>
      </CardFooter>
    </Card>
  )
}
import EsuissUI
import SwiftUI

struct CardWithImageExample: View {
  var body: some View {
    Card {
      EsuissColor.muted
        .aspectRatio(16.0 / 9.0, contentMode: .fit)
        .overlay {
          AsyncImage(url: CardImage.url) { phase in
            if let image = phase.image {
              image.resizable().scaledToFill()
            }
          }
        }
        .clipped()
        .padding(.top, -24)
        .accessibilityHidden(true)
      CardHeader("Mountain retreat", description: "A quiet cabin with a view.")
      CardFooter {
        Button("Book now") {}
          .buttonStyle(.variant(.tinted, block: true))
      }
    }
  }
}

enum CardImage {
  static let url = URL(string: "https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80")
}

let url = URL(string: "https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80")
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.layout.layout
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonVariant
import com.esuiss.ui.components.Card
import com.esuiss.ui.components.CardFooter
import com.esuiss.ui.components.CardHeader
import com.esuiss.ui.components.CardSize
import com.esuiss.ui.theme.EsuissTheme
import com.esuiss.ui.theme.Palette

@Composable
fun CardWithImage() {
    val colors = EsuissTheme.colors
    val inset = CardSize.Default.spacing
    Card {
        Box(
            Modifier
                .layout { measurable, constraints ->
                    val placeable = measurable.measure(constraints)
                    val shift = inset.roundToPx()
                    layout(placeable.width, placeable.height - shift) { placeable.place(0, -shift) }
                }
                .fillMaxWidth()
                .aspectRatio(16f / 9f)
                .background(Brush.linearGradient(listOf(Palette.teal500, Palette.blue500, Palette.indigo500))),
        )
        CardHeader("Mountain retreat", description = "A quiet cabin with a view.")
        CardFooter {
            Button("Book now", onClick = {}, variant = ButtonVariant.Tinted, block = true)
        }
    }
}

Filled

The flat filled variant.

iOS
Card (filled) on iOS in light modeCard (filled) on iOS in dark mode
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Text } from "@/components/ui/text"

export function CardFilledExample() {
  return (
    <Card variant="filled" className="w-full">
      <CardHeader>
        <CardTitle>Storage</CardTitle>
        <CardDescription>42 GB of 128 GB used</CardDescription>
      </CardHeader>
      <CardContent>
        <Text className="text-sm text-muted-foreground">
          Photos and backups take up most of your space.
        </Text>
      </CardContent>
    </Card>
  )
}
import EsuissUI
import SwiftUI

struct CardFilledExample: View {
  var body: some View {
    Card(.filled) {
      CardHeader("Storage", description: "42 GB of 128 GB used")
      CardContent {
        Text("Photos and backups take up most of your space.")
          .textStyle(.sm)
          .foregroundStyle(EsuissColor.mutedForeground)
      }
    }
  }
}
import androidx.compose.runtime.Composable
import com.esuiss.ui.components.Card
import com.esuiss.ui.components.CardContent
import com.esuiss.ui.components.CardHeader
import com.esuiss.ui.components.CardVariant
import com.esuiss.ui.components.Text
import com.esuiss.ui.theme.EsuissTheme
import com.esuiss.ui.theme.Typography

@Composable
fun CardFilled() {
    Card(variant = CardVariant.Filled) {
        CardHeader("Storage", description = "42 GB of 128 GB used")
        CardContent {
            Text(
                "Photos and backups take up most of your space.",
                style = Typography.sm.copy(color = EsuissTheme.colors.mutedForeground),
            )
        }
    }
}

API Reference

01

Card

Outer container that sets the size and variant.

PropTypeDefaultDescription
sizeCardSize"default"default or sm.
variantCardVariant"default"default (raised) or filled.

Also accepts the props of React.ComponentProps<typeof View>, React.RefAttributes<View>.

02

CardHeader

Title area; places a CardAction beside the text.

Also accepts the props of React.ComponentProps<typeof View>, React.RefAttributes<View>.

03

CardTitle

Bold heading.

Also accepts the props of React.ComponentProps<typeof Text>, React.RefAttributes<typeof Text>.

04

CardDescription

Secondary text below the title.

Also accepts the props of React.ComponentProps<typeof Text>, React.RefAttributes<typeof Text>.

05

CardAction

Slot for a button in the header.

Also accepts the props of React.ComponentProps<typeof View>, React.RefAttributes<View>.

06

CardContent

Padded body.

Also accepts the props of React.ComponentProps<typeof View>, React.RefAttributes<View>.

07

CardFooter

Padded row for actions.

Also accepts the props of React.ComponentProps<typeof View>, React.RefAttributes<View>.

Accessibility

  • CardTitle has the heading role.
  • The card itself adds no role; actions inside provide their own.

Notes

  • Uses bg-card with a shadow, or bg-muted for filled; the border is stronger in dark mode.
  • Light and dark follow the system appearance.
NextCarouselPreviousCalendar

On this page

  1. 01Installation
    • CLI
    • Manual
  2. 02Usage
  3. 03Examples
    • Small
    • Filled
    • With action
    • With borders
    • With image
  4. 04API Reference
    • Card
    • CardHeader
    • CardTitle
    • CardDescription
    • CardAction
    • CardContent
    • CardFooter
  5. 05Accessibility
  6. 06Notes
  7. 07Installation
    • Manual
  8. 08Usage
  9. 09Examples
    • Small
    • With action
    • With image
    • Filled
  10. 10API Reference
    • Card
    • CardHeader
    • CardTitle
    • CardDescription
    • CardAction
    • CardContent
    • CardFooter
  11. 11Accessibility
  12. 12Notes