PixelFeatureCard

Feature highlight card with toned icon frame, optional badge, title, clamped description, and footer — renders as <article>, role="button", or <a href> with full-card click target.

  • Toned icon frame (48/56/64/80px) with surface-aware border and soft background
  • Optional badge slot above the icon with independent tone
  • Vertical or horizontal orientation with consistent alignment
  • Polymorphic root: <article>, interactive role="button" with Enter/Space, or <a href> with full-card target
  • Clamped description (2/3/4 lines) keeps cards aligned in grids
Status
stable
Category
cards
Since
v1.7.0

API

import { PixelFeatureCard } from '@pxlkit/ui-kit';
PropTypeDefaultDescription
iconReact.ReactNode—Icon in the toned frame.
iconSize48 | 56 | 64 | 8056Width of the icon frame, in px.
badge{ label: string; tone?: ToneKey }—Badge above the icon: its label and tone (cyan by default).
title (required)string—The heading, clamped to two lines.
descriptionstring—Muted paragraph rendered under the title.
descriptionLines2 | 3 | 4—Apply line-clamp-N + min-h-[N lh] to the description.
descstring—Deprecated. Use description for consistency with PixelCard / PixelPricingCard.
descLines2 | 3 | 4—Deprecated. Use descriptionLines for consistency with PixelCard / PixelPricingCard.
footerReact.ReactNode—Footer under the description.
tone'neutral' | 'green' | 'cyan' | 'gold' | 'red' | 'purple' | 'pink''neutral'Tone of the icon frame.
interactivebooleanfalseHover lift and focus ring; without an href the card is a button: give it an onClick.
hrefstring—When provided, the card renders as <a href> and accepts anchor-specific attributes via the spread. Nesting interactive children inside the card (e.g. PixelButton, PixelTextLink) is invalid HTML in this mode.
target'_self' | '_blank' | '_parent' | '_top' | (string & {})—Anchor target — only meaningful when href is set.
relstring—Anchor rel — only meaningful when href is set.
downloadany—Anchor download — only meaningful when href is set.
onClickReact.MouseEventHandler<HTMLElement>—When interactive=true without href, an onClick is REQUIRED for accessibility.
orientation'vertical' | 'horizontal''vertical'Icon above the text, or beside it.
surface'pixel' | 'linear'—Surface override; defaults to the nearest provider.
borderedbooleantrueRender with surface-aware border + radius chrome. Defaults to true — a feature card needs visible chrome.

Also takes the native attributes and event handlers of its element (HTMLAttributes<HTMLElement>).

ref points to its element (HTMLElement).

Accessibility

WCAG target: 2.1 AA

  • button
  • link

Semantic <article> by default. When interactive without href, the root becomes <div role="button" tabindex="0"> with Enter/Space activation parity (<article> does not permit role="button"). When href is set, the root renders as a native <a> with the entire card as the click target — nesting interactive children (PixelButton, PixelTextLink) inside footer is invalid HTML in href mode and breaks screen reader navigation. Focus-visible ring is provided automatically in both interactive modes.

Keyboard

KeyActionWhen
EnterActivates the card when interactive (role="button") or follows the link when href is set—
SpaceActivates the card when interactive (role="button")interactive prop is true and href is not set
TabMoves focus to the card when interactive or anchored—

Usage

import { PixelFeatureCard } from '@pxlkit/ui-kit';

export function Default() {
  return (
    <PixelFeatureCard
      title="Realtime sync"
      description="Push every keystroke to peers via WebSockets — under 50ms p95."
    />
  );
}

Examples

Default

import { PixelFeatureCard } from '@pxlkit/ui-kit';

export function Default() {
  return (
    <PixelFeatureCard
      title="Realtime sync"
      description="Push every keystroke to peers via WebSockets — under 50ms p95."
    />
  );
}

With Icon

import { PixelFeatureCard } from '@pxlkit/ui-kit';

const PixelIcon = (
  <svg viewBox="0 0 8 8" shapeRendering="crispEdges" fill="currentColor" className="h-4 w-4">
    <rect x="3" y="0" width="2" height="8" />
    <rect x="0" y="3" width="8" height="2" />
  </svg>
);

export function WithIcon() {
  return (
    <PixelFeatureCard
      icon={PixelIcon}
      title="Pixel-perfect"
      description="Crisp edges on every retina ratio thanks to shape-rendering: crispEdges."
    />
  );
}

With Badge

import { PixelFeatureCard } from '@pxlkit/ui-kit';

const PixelIcon = (
  <svg viewBox="0 0 8 8" shapeRendering="crispEdges" fill="currentColor" className="h-4 w-4">
    <rect x="3" y="0" width="2" height="8" />
    <rect x="0" y="3" width="8" height="2" />
  </svg>
);

export function WithBadge() {
  return (
    <PixelFeatureCard
      icon={PixelIcon}
      badge={{ label: 'NEW', tone: 'gold' }}
      title="AI Companion"
      description="A built-in copilot that learns your codebase as you ship it."
    />
  );
}

Tones

import { PixelFeatureCard } from '@pxlkit/ui-kit';

const PixelIcon = (
  <svg viewBox="0 0 8 8" shapeRendering="crispEdges" fill="currentColor" className="h-4 w-4">
    <rect x="3" y="0" width="2" height="8" />
    <rect x="0" y="3" width="8" height="2" />
  </svg>
);

export function Tones() {
  return (
    <div className="grid grid-cols-2 gap-3">
      <PixelFeatureCard icon={PixelIcon} tone="cyan" title="Cyan" description="Tinted icon frame." />
      <PixelFeatureCard icon={PixelIcon} tone="green" title="Green" description="Tinted icon frame." />
      <PixelFeatureCard icon={PixelIcon} tone="gold" title="Gold" description="Tinted icon frame." />
      <PixelFeatureCard icon={PixelIcon} tone="purple" title="Purple" description="Tinted icon frame." />
    </div>
  );
}

Surfaces

import { PixelFeatureCard } from '@pxlkit/ui-kit';

const PixelIcon = (
  <svg viewBox="0 0 8 8" shapeRendering="crispEdges" fill="currentColor" className="h-4 w-4">
    <rect x="3" y="0" width="2" height="8" />
    <rect x="0" y="3" width="8" height="2" />
  </svg>
);

export function Surfaces() {
  return (
    <div className="grid grid-cols-2 gap-3">
      <PixelFeatureCard surface="pixel" icon={PixelIcon} title="Pixel" description="Thick border + offset shadow." />
      <PixelFeatureCard surface="linear" icon={PixelIcon} title="Linear" description="Soft border + smooth radius." />
    </div>
  );
}

Horizontal

import { PixelFeatureCard } from '@pxlkit/ui-kit';

const PixelIcon = (
  <svg viewBox="0 0 8 8" shapeRendering="crispEdges" fill="currentColor" className="h-4 w-4">
    <rect x="3" y="0" width="2" height="8" />
    <rect x="0" y="3" width="8" height="2" />
  </svg>
);

export function Horizontal() {
  return (
    <PixelFeatureCard
      orientation="horizontal"
      icon={PixelIcon}
      title="Horizontal layout"
      description="Icon sits to the left of the title and description."
    />
  );
}

Interactive

import { PixelFeatureCard } from '@pxlkit/ui-kit';

const PixelIcon = (
  <svg viewBox="0 0 8 8" shapeRendering="crispEdges" fill="currentColor" className="h-4 w-4">
    <rect x="3" y="0" width="2" height="8" />
    <rect x="0" y="3" width="8" height="2" />
  </svg>
);

export function Interactive() {
  return (
    <PixelFeatureCard
      interactive
      onClick={() => alert('feature clicked')}
      icon={PixelIcon}
      title="Click me"
      description="Press Enter or Space to activate via keyboard."
    />
  );
}

Clamped Description

import { PixelFeatureCard } from '@pxlkit/ui-kit';

const PixelIcon = (
  <svg viewBox="0 0 8 8" shapeRendering="crispEdges" fill="currentColor" className="h-4 w-4">
    <rect x="3" y="0" width="2" height="8" />
    <rect x="0" y="3" width="8" height="2" />
  </svg>
);

export function ClampedDescription() {
  return (
    <PixelFeatureCard
      icon={PixelIcon}
      title="Long Description"
      description="This description is intentionally long to demonstrate the line-clamp behavior. It will be truncated to the configured number of lines with an ellipsis, while maintaining a minimum height so cards stay aligned in a grid."
      descriptionLines={2}
    />
  );
}

Icon Sizes

import { PixelFeatureCard } from '@pxlkit/ui-kit';

const PixelIcon = (
  <svg viewBox="0 0 8 8" shapeRendering="crispEdges" fill="currentColor" className="h-4 w-4">
    <rect x="3" y="0" width="2" height="8" />
    <rect x="0" y="3" width="8" height="2" />
  </svg>
);

export function IconSizes() {
  return (
    <div className="grid grid-cols-2 gap-3">
      <PixelFeatureCard icon={PixelIcon} iconSize={48} title="Small" description="48px icon frame." />
      <PixelFeatureCard icon={PixelIcon} iconSize={80} title="Large" description="80px icon frame." />
    </div>
  );
}

Related