reelcn
Product

Product

Feature Card

Icon, title, body copy and an optional badge on a themed card that rises into place.

Install

Usage

<Center>
  <FeatureCard title="Zero config" body="Install with shadcn, no path aliases." badge="New" />
</Center>

Props

NameTypeDefaultDescription
delay?numberFrames to wait before entering.
duration?numberEnter duration in frames. Defaults to 0.6s.
exit?number | booleanExit at the end of the parent `<Sequence>`. `false` keeps it on screen, a number sets the exit length in frames.
motion?MotionPresetOverride the theme's motion personality.
icon?ReactNodeSmall mark shown in a rounded well above the title, e.g. an inline `<svg>`.
titlestring
body?string
badge?stringSmall pill in the top-right corner, e.g. "New" or "Popular".
width?number420Card width in design units.
background?string
borderColor?string
titleColor?string
bodyColor?string
iconColor?stringIcon well color, and the badge's default color. Defaults to the theme accent.
iconBackground?stringIcon well fill. Defaults to a soft tint of `iconColor`.
badgeColor?string
badgeBackground?string
radius?number
style?CSSProperties
className?string

Use

  • One feature at a time in a launch video, or several staggered inside `stagger` or `bento-grid`
  • A pricing tier or plan card with a "New" or "Popular" badge

Avoid

  • Several feature tiles on one canvas at once — use bento-grid

Dependencies

Source

registry/items/feature-card.tsx
/**
 * @title Feature Card
 * @category product
 * @description Icon, title, body copy and an optional badge on a themed card that rises into place.
 * @duration 24
 * @use One feature at a time in a launch video, or several staggered inside `stagger` or `bento-grid`
 * @use A pricing tier or plan card with a "New" or "Popular" badge
 * @avoid Several feature tiles on one canvas at once — use `bento-grid`
 * @tags feature, card, icon, badge, pricing, plan
 * @example
 * <Center>
 *   <FeatureCard title="Zero config" body="Install with shadcn, no path aliases." badge="New" />
 * </Center>
 */
import type React from "react";
import { effectStyle } from "./animate";
import { alpha, type MotionProps, useMotion, useTheme, useViewport } from "./core";

export type FeatureCardProps = MotionProps & {
  /** Small mark shown in a rounded well above the title, e.g. an inline `<svg>`. */
  icon?: React.ReactNode;
  title: string;
  body?: string;
  /** Small pill in the top-right corner, e.g. "New" or "Popular". */
  badge?: string;
  /** Card width in design units. */
  width?: number;
  background?: string;
  borderColor?: string;
  titleColor?: string;
  bodyColor?: string;
  /** Icon well color, and the badge's default color. Defaults to the theme accent. */
  iconColor?: string;
  /** Icon well fill. Defaults to a soft tint of `iconColor`. */
  iconBackground?: string;
  badgeColor?: string;
  badgeBackground?: string;
  radius?: number;
  style?: React.CSSProperties;
  className?: string;
};

export function FeatureCard({
  icon,
  title,
  body,
  badge,
  width = 420,
  background,
  borderColor,
  titleColor,
  bodyColor,
  iconColor,
  iconBackground,
  badgeColor,
  badgeBackground,
  radius,
  style,
  className,
  ...motion
}: FeatureCardProps) {
  const theme = useTheme();
  const { u } = useViewport();
  const m = useMotion(motion);
  const accent = iconColor ?? theme.colors.accent;
  const { opacity: riseOpacity = 1, ...rise } = effectStyle("up", m.enter, u(46));

  return (
    <div
      className={className}
      style={{
        position: "relative",
        width: u(width),
        padding: u(32),
        borderRadius: u(radius ?? theme.radius),
        background: background ?? theme.colors.surface,
        border: `1px solid ${borderColor ?? theme.colors.border}`,
        fontFamily: theme.fonts.body,
        opacity: Number(riseOpacity) * (1 - m.exit),
        ...rise,
        ...style,
      }}
    >
      {badge && (
        <div
          style={{
            position: "absolute",
            top: u(20),
            right: u(20),
            padding: `${u(4)}px ${u(12)}px`,
            borderRadius: 9999,
            background: badgeBackground ?? alpha(accent, 0.16),
            color: badgeColor ?? accent,
            fontSize: u(16),
            fontWeight: 700,
            letterSpacing: "0.04em",
            textTransform: "uppercase",
          }}
        >
          {badge}
        </div>
      )}
      {icon && (
        <div
          style={{
            width: u(56),
            height: u(56),
            borderRadius: u(theme.radius * 0.7),
            display: "grid",
            placeItems: "center",
            background: iconBackground ?? alpha(accent, 0.14),
            color: accent,
            fontSize: u(28),
          }}
        >
          {icon}
        </div>
      )}
      <div
        style={{
          marginTop: icon ? u(22) : 0,
          fontFamily: theme.fonts.heading,
          fontWeight: theme.headingWeight,
          fontSize: u(34),
          lineHeight: 1.15,
          letterSpacing: "-0.02em",
          color: titleColor ?? theme.colors.foreground,
        }}
      >
        {title}
      </div>
      {body && (
        <div style={{ marginTop: u(10), fontSize: u(22), lineHeight: 1.4, color: bodyColor ?? theme.colors.muted }}>
          {body}
        </div>
      )}
    </div>
  );
}

Related