Skip to main content
Nexus Design System
GitHub

Card

Card groups related content and actions in a bordered section. Use it for dashboards, settings sections and forms. For a plain list row, use Item.

Card Title

Card description goes here.

Card content goes here.

'use client';

import { Button } from '@/components/nxds/components/button/button';
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from '@/components/nxds/components/card/card';

export default function CardDefault() {
  return (
    <Card className="nx:w-[350px]">
      <CardHeader>
        <CardTitle>Card Title</CardTitle>
        <CardDescription>Card description goes here.</CardDescription>
      </CardHeader>
      <CardContent>
        <p>Card content goes here.</p>
      </CardContent>
      <CardFooter>
        <Button variant="outline">Cancel</Button>
        <Button>Submit</Button>
      </CardFooter>
    </Card>
  );
}

This example also needs: Button.

Installation

npx nxds-cli@latest add card

Run npx nxds-cli@latest init first if nxds isn’t set up in your app yet. See Install.

Usage

import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from '@/components/nxds/components/card/card';

Composition

Card
├── CardHeader
│   ├── CardTitle
│   ├── CardDescription
│   └── CardAction
├── CardContent
└── CardFooter

Examples

With action

Put a header action beside the title with CardAction.

Card with Action

This card has an action button.

Content with an action button in the header.

'use client';

import { Button } from '@/components/nxds/components/button/button';
import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from '@/components/nxds/components/card/card';

export default function CardWithAction() {
  return (
    <Card className="nx:w-[350px]">
      <CardHeader>
        <CardTitle>Card with Action</CardTitle>
        <CardDescription>This card has an action button.</CardDescription>
        <CardAction>
          <Button size="sm" variant="outline">
            Edit
          </Button>
        </CardAction>
      </CardHeader>
      <CardContent>
        <p>Content with an action button in the header.</p>
      </CardContent>
    </Card>
  );
}

Place action buttons at the bottom with CardFooter.

Card with Footer

This card demonstrates footer usage.

The footer is useful for action buttons.

'use client';

import { Button } from '@/components/nxds/components/button/button';
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from '@/components/nxds/components/card/card';

export default function CardWithFooter() {
  return (
    <Card className="nx:w-[350px]">
      <CardHeader>
        <CardTitle>Card with Footer</CardTitle>
        <CardDescription>This card demonstrates footer usage.</CardDescription>
      </CardHeader>
      <CardContent>
        <p>The footer is useful for action buttons.</p>
      </CardContent>
      <CardFooter>
        <Button variant="outline">Cancel</Button>
        <Button>Save Changes</Button>
      </CardFooter>
    </Card>
  );
}

Content only

Use a card with content only, without a header or footer.

A card with only content, no header or footer.

'use client';

import { Card, CardContent } from '@/components/nxds/components/card/card';

export default function CardContentOnly() {
  return (
    <Card className="nx:w-[350px]">
      <CardContent className="nx:pt-6">
        <p>A card with only content, no header or footer.</p>
      </CardContent>
    </Card>
  );
}

Login card

Wrap a sign-in form in a card with a header and footer.

Sign in

Enter your email and password to continue.

'use client';

import * as React from 'react';

import { Button } from '@/components/nxds/components/button/button';
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from '@/components/nxds/components/card/card';
import { Input } from '@/components/nxds/components/input/input';
import { Label } from '@/components/nxds/components/label/label';

export default function CardLoginCard() {
  const emailId = React.useId();
  const passwordId = React.useId();

  return (
    <form
      className="nx:w-full nx:max-w-sm"
      onSubmit={(event) => event.preventDefault()}
    >
      <Card>
        <CardHeader>
          <CardTitle>Sign in</CardTitle>
          <CardDescription>
            Enter your email and password to continue.
          </CardDescription>
        </CardHeader>
        <CardContent className="nx:grid nx:gap-4">
          <div className="nx:grid nx:gap-1.5">
            <Label htmlFor={emailId}>Email</Label>
            <Input
              id={emailId}
              type="email"
              autoComplete="email"
              placeholder="you@example.com"
              required
            />
          </div>
          <div className="nx:grid nx:gap-1.5">
            <Label htmlFor={passwordId}>Password</Label>
            <Input
              id={passwordId}
              type="password"
              autoComplete="current-password"
              required
            />
          </div>
        </CardContent>
        <CardFooter>
          <Button type="submit" className="nx:w-full">
            Sign in
          </Button>
        </CardFooter>
      </Card>
    </form>
  );
}

This example also needs: Input, Label.

Props

Card

Card adds no props of its own; it accepts the props of the element or primitive it wraps.

CardAction

CardAction adds no props of its own; it accepts the props of the element or primitive it wraps.

CardContent

CardContent adds no props of its own; it accepts the props of the element or primitive it wraps.

CardDescription

CardDescription adds no props of its own; it accepts the props of the element or primitive it wraps.

CardFooter adds no props of its own; it accepts the props of the element or primitive it wraps.

CardHeader

CardHeader adds no props of its own; it accepts the props of the element or primitive it wraps.

CardTitle

CardTitle props
PropTypeDefaultDescription
asChildbooleanfalse

Applies the title styles to the child element, such as a different heading level.

Card groups related content and actions in a bordered section. Use it for dashboards, settings sections and forms.

<script setup lang="ts">
import { Button } from '@/components/button';
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from '@/components/card';
</script>

<template>
  <Card class="nx:w-[350px]">
    <CardHeader>
      <CardTitle>Card Title</CardTitle>
      <CardDescription>Card description goes here.</CardDescription>
    </CardHeader>
    <CardContent>
      <p>Card content goes here.</p>
    </CardContent>
    <CardFooter>
      <Button variant="outline">Cancel</Button>
      <Button>Submit</Button>
    </CardFooter>
  </Card>
</template>

This example also needs: Button.

Installation

  1. Install the following dependencies:

    npm install reka-ui@^2.11.0
    
  2. Copy and paste the following code into your project.

    components/card/Card.vue
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    export interface CardProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<CardProps>();
    </script>
    
    <template>
      <div
        data-slot="card"
        :class="
          cn(
            'nx:overflow-hidden nx:rounded-xl nx:border-width-default nx:border-color-default nx:bg-container nx:text-container-foreground nx:shadow-sm',
            props.class
          )
        "
      >
        <slot />
      </div>
    </template>
    
    components/card/CardAction.vue
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    export interface CardActionProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<CardActionProps>();
    </script>
    
    <template>
      <div
        data-slot="card-action"
        :class="
          cn(
            'nx:col-start-2 nx:row-start-1 nx:row-span-2 nx:flex nx:items-center nx:gap-2 nx:self-start nx:justify-self-end nx:pl-4',
            props.class
          )
        "
      >
        <slot />
      </div>
    </template>
    
    components/card/CardContent.vue
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    export interface CardContentProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<CardContentProps>();
    </script>
    
    <template>
      <div data-slot="card-content" :class="cn('nx:p-6 nx:pt-0', props.class)">
        <slot />
      </div>
    </template>
    
    components/card/CardDescription.vue
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    export interface CardDescriptionProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<CardDescriptionProps>();
    </script>
    
    <template>
      <p
        data-slot="card-description"
        :class="
          cn(
            'nx:col-start-1 nx:m-0 nx:min-w-0 nx:typography-body-default nx:text-muted-foreground',
            props.class
          )
        "
      >
        <slot />
      </p>
    </template>
    
    components/card/CardFooter.vue
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    export interface CardFooterProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<CardFooterProps>();
    </script>
    
    <template>
      <div
        data-slot="card-footer"
        :class="cn('nx:flex nx:items-center nx:gap-2 nx:p-6 nx:pt-0', props.class)"
      >
        <slot />
      </div>
    </template>
    
    components/card/CardHeader.vue
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    export interface CardHeaderProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<CardHeaderProps>();
    </script>
    
    <template>
      <div
        data-slot="card-header"
        :class="
          cn(
            'nx:grid nx:grid-cols-[minmax(0,1fr)_auto] nx:items-start nx:gap-y-1.5 nx:p-6',
            props.class
          )
        "
      >
        <slot />
      </div>
    </template>
    
    components/card/CardTitle.vue
    <script setup lang="ts">
    import { Primitive, type PrimitiveProps } from 'reka-ui';
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    export interface CardTitleProps extends PrimitiveProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = withDefaults(defineProps<CardTitleProps>(), { as: 'h3' });
    </script>
    
    <template>
      <Primitive
        data-slot="card-title"
        :as="as"
        :as-child="asChild"
        :class="
          cn(
            'nx:col-start-1 nx:m-0 nx:min-w-0 nx:typography-heading-xsmall',
            props.class
          )
        "
      >
        <slot />
      </Primitive>
    </template>
    
    components/card/index.ts
    export { default as Card, type CardProps } from './Card.vue';
    export { default as CardAction, type CardActionProps } from './CardAction.vue';
    export {
      default as CardContent,
      type CardContentProps,
    } from './CardContent.vue';
    export {
      default as CardDescription,
      type CardDescriptionProps,
    } from './CardDescription.vue';
    export { default as CardFooter, type CardFooterProps } from './CardFooter.vue';
    export { default as CardHeader, type CardHeaderProps } from './CardHeader.vue';
    export { default as CardTitle, type CardTitleProps } from './CardTitle.vue';
    
  3. Keep each file at the path shown above it.

    Paths are relative to packages/vue/src/. Keep the same folders under your @/ root, and the imports between files work without edits.

Usage

import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/card';

Examples

With action

Put a header action beside the title with CardAction.

<script setup lang="ts">
import { Button } from '@/components/button';
import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from '@/components/card';
</script>

<template>
  <Card class="nx:w-[350px]">
    <CardHeader>
      <CardTitle>Card with Action</CardTitle>
      <CardDescription>This card has an action button.</CardDescription>
      <CardAction>
        <Button size="sm" variant="outline">Edit</Button>
      </CardAction>
    </CardHeader>
    <CardContent>
      <p>Content with an action button in the header.</p>
    </CardContent>
  </Card>
</template>

Place action buttons at the bottom with CardFooter.

<script setup lang="ts">
import { Button } from '@/components/button';
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from '@/components/card';
</script>

<template>
  <Card class="nx:w-[350px]">
    <CardHeader>
      <CardTitle>Card with Footer</CardTitle>
      <CardDescription>This card demonstrates footer usage.</CardDescription>
    </CardHeader>
    <CardContent>
      <p>The footer is useful for action buttons.</p>
    </CardContent>
    <CardFooter>
      <Button variant="outline">Cancel</Button>
      <Button>Save Changes</Button>
    </CardFooter>
  </Card>
</template>

Content only

Use a card with content only, without a header or footer.

<script setup lang="ts">
import { Card, CardContent } from '@/components/card';
</script>

<template>
  <Card class="nx:w-[350px]">
    <CardContent class="nx:pt-6">
      <p>A card with only content, no header or footer.</p>
    </CardContent>
  </Card>
</template>

Login card

Wrap a sign-in form in a card with a header and footer.

<script setup lang="ts">
import { useId } from 'vue';

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

const emailId = useId();
const passwordId = useId();
</script>

<template>
  <form class="nx:w-full nx:max-w-sm" @submit.prevent>
    <Card>
      <CardHeader>
        <CardTitle>Sign in</CardTitle>
        <CardDescription
          >Enter your email and password to continue.</CardDescription
        >
      </CardHeader>
      <CardContent class="nx:grid nx:gap-4">
        <div class="nx:grid nx:gap-1.5">
          <label :for="emailId">Email</label>
          <input
            :id="emailId"
            type="email"
            autocomplete="email"
            placeholder="you@example.com"
            required
          />
        </div>
        <div class="nx:grid nx:gap-1.5">
          <label :for="passwordId">Password</label>
          <input
            :id="passwordId"
            type="password"
            autocomplete="current-password"
            required
          />
        </div>
      </CardContent>
      <CardFooter>
        <Button type="submit" class="nx:w-full">Sign in</Button>
      </CardFooter>
    </Card>
  </form>
</template>

Props

Card

Card adds no props of its own; it accepts the props of the element or primitive it wraps.

CardAction

CardAction adds no props of its own; it accepts the props of the element or primitive it wraps.

CardContent

CardContent adds no props of its own; it accepts the props of the element or primitive it wraps.

CardDescription

CardDescription adds no props of its own; it accepts the props of the element or primitive it wraps.

CardFooter adds no props of its own; it accepts the props of the element or primitive it wraps.

CardHeader

CardHeader adds no props of its own; it accepts the props of the element or primitive it wraps.

CardTitle

CardTitle adds no props of its own; it accepts the props of the element or primitive it wraps.