Skip to content
Noksha UIv0.1
Colour theme

display

Card

A surface with header, content and footer parts for grouping related content.

Examples

Anatomy

Production

Healthy

Deployed 12 minutes ago from main.

Region
eu-west-1
Instances
6
p99 latency
84 ms

Variants

Four surfaces, from raised to no chrome at all.

elevated

The surface changes; the parts inside do not.

outline

The surface changes; the parts inside do not.

subtle

The surface changes; the parts inside do not.

ghost

The surface changes; the parts inside do not.

Own the source

Noksha ships as a package and as copy-paste source. Take the files and they are yours to change. These are read from the generated registry, so they are exactly what the library ships — never a paraphrase of it.

Self-contained apart from @noksha-ui/core. Or let the CLI do it: npx @noksha-ui/cli add card writes them, follows the same dependency graph, and fixes up the imports.

import { Slot } from '@noksha-ui/core';
import * as React from 'react';
import type { CardPartProps, CardProps, CardTitleProps } from './card.types.js';
import {
  cardContentVariants,
  cardDescriptionVariants,
  cardFooterVariants,
  cardHeaderVariants,
  cardTitleVariants,
  cardVariants,
} from './card.variants.js';

/**
 * A surface that groups related content.
 *
 * ```tsx
 * <Card.Root>
 *   <Card.Header>
 *     <Card.Title>Usage</Card.Title>
 *     <Card.Description>Billing period to date</Card.Description>
 *   </Card.Header>
 *   <Card.Content>…</Card.Content>
 *   <Card.Footer><Button>Upgrade</Button></Card.Footer>
 * </Card.Root>
 * ```
 */
export const CardRoot = React.forwardRef<HTMLDivElement, CardProps>(function CardRoot(
  {
    variant = 'elevated',
    padding = 'md',
    interactive = false,
    asChild = false,
    className,
    ...rest
  },
  ref,
) {
  const Comp = asChild ? Slot : 'div';

  return (
    <Comp
      ref={ref}
      data-interactive={interactive || undefined}
      className={cardVariants({ variant, padding, interactive, className })}
      {...rest}
    />
  );
});
CardRoot.displayName = 'Card.Root';

export const CardHeader = React.forwardRef<HTMLDivElement, CardPartProps>(function CardHeader(
  { asChild = false, className, ...rest },
  ref,
) {
  const Comp = asChild ? Slot : 'div';
  return <Comp ref={ref} className={cardHeaderVariants({ className })} {...rest} />;
});
CardHeader.displayName = 'Card.Header';

/**
 * Defaults to `<h3>` but takes any level through `as`.
 *
 * There is no correct default heading level for a component that does not know
 * its page, so this exposes the choice rather than hard-coding one and quietly
 * breaking the document outline.
 */
export const CardTitle = React.forwardRef<HTMLHeadingElement, CardTitleProps>(function CardTitle(
  { as: Tag = 'h3', asChild = false, className, ...rest },
  ref,
) {
  const Comp = asChild ? Slot : Tag;
  return <Comp ref={ref} className={cardTitleVariants({ className })} {...rest} />;
});
CardTitle.displayName = 'Card.Title';

export const CardDescription = React.forwardRef<HTMLParagraphElement, CardPartProps>(
  function CardDescription({ asChild = false, className, ...rest }, ref) {
    const Comp = asChild ? Slot : 'p';
    return <Comp ref={ref} className={cardDescriptionVariants({ className })} {...rest} />;
  },
);
CardDescription.displayName = 'Card.Description';

export const CardContent = React.forwardRef<HTMLDivElement, CardPartProps>(function CardContent(
  { asChild = false, className, ...rest },
  ref,
) {
  const Comp = asChild ? Slot : 'div';
  return <Comp ref={ref} className={cardContentVariants({ className })} {...rest} />;
});
CardContent.displayName = 'Card.Content';

export const CardFooter = React.forwardRef<HTMLDivElement, CardPartProps>(function CardFooter(
  { asChild = false, className, ...rest },
  ref,
) {
  const Comp = asChild ? Slot : 'div';
  return <Comp ref={ref} className={cardFooterVariants({ className })} {...rest} />;
});
CardFooter.displayName = 'Card.Footer';

/**
 * The namespace is a plain object of the same components exported individually
 * above, so `import { CardTitle }` tree-shakes even where `Card.Title` reads
 * better in application code.
 */
export const Card = {
  Root: CardRoot,
  Header: CardHeader,
  Title: CardTitle,
  Description: CardDescription,
  Content: CardContent,
  Footer: CardFooter,
};

export { cardVariants };

API reference

Accepted values

CardVariant
elevatedoutlinesubtleghost
CardPadding
nonesmmdlg

CardProps

Also accepts everything from React.HTMLAttributes<HTMLDivElement>.

PropTypeDescription
variantCardVariant
paddingCardPaddingApplied to the header, content and footer parts — not to the root.
interactivebooleanAdds hover and press affordances. Does **not** make the card focusable on its own: use `asChild` with an `<a>` or a `<button>` for that, so the whole card carries real semantics instead of a click handler on a `<div>`.
asChildboolean

CardPartProps

Also accepts everything from React.HTMLAttributes<HTMLDivElement>.

PropTypeDescription
asChildboolean

CardTitleProps

Also accepts everything from React.HTMLAttributes<HTMLHeadingElement>.

PropTypeDescription
as'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'The heading level. Pick the one that fits the page outline.
asChildboolean