Account

Cards

Free

Feature Card

The icon tile turns and lifts, a hairline sweeps under the title, and a 'learn more' arrow slides in.

Install

No key needed.

Terminal
npx @galactrex/ui add feature-card

Usage

Example
import { FeatureCard } from "@/components/galactrex/feature-card";

<FeatureCard icon={<LayersIcon />} title="Layers">Masks and blend modes.</FeatureCard>

Source

Installs to components/galactrex/feature-card.tsx, with the shared motion primitives beside it.

components/galactrex/feature-card.tsx
"use client";
import type { ReactNode } from "react";
import { motion } from "motion/react";
import { cx } from "@/components/galactrex/motion/cx";
import { curves } from "@/components/galactrex/motion/springs";
import { useReducedMotion } from "@/components/galactrex/motion/use-reduced-motion";

export type FeatureCardProps = {
  /** an icon, 24px works best */
  icon: ReactNode;
  title: string;
  children: ReactNode;
  /** a link for "learn more" */
  href?: string;
  /** a number or step label, like "01" */
  index?: string;
  className?: string;
};

/** The feature card, done with care: the icon sits in a tile that turns and lifts on hover, a hairline sweeps
    under the title, and a "learn more" arrow slides in. For feature grids, process steps and service lists. One
    focusable link when it has `href`. */
export function FeatureCard({ icon, title, children, href, index, className }: FeatureCardProps) {
  const reduced = useReducedMotion();
  const Tag = href ? motion.a : motion.div;
  return (
    <Tag
      href={href}
      initial="rest"
      animate="rest"
      whileHover={reduced ? undefined : "on"}
      whileFocus={reduced ? undefined : "on"}
      className={cx("group relative block rounded-2xl border border-gxui-line bg-gxui-surface p-6 text-gxui-ink outline-none transition-colors hover:border-gxui-muted focus-visible:ring-2 focus-visible:ring-gxui-ink", className)}
    >
      <div className="flex items-start justify-between">
        <motion.span aria-hidden className="grid size-11 place-items-center rounded-xl border border-gxui-line bg-gxui-raised" variants={{ rest: { rotate: 0, y: 0 }, on: { rotate: -8, y: -3 } }} transition={{ type: "spring", stiffness: 380, damping: 18 }}>
          {icon}
        </motion.span>
        {index && <span className="font-mono text-xs text-gxui-muted">{index}</span>}
      </div>
      <p className="relative mt-5 inline-block font-semibold">
        {title}
        <motion.span aria-hidden className="absolute -bottom-1 left-0 h-px w-full origin-left bg-gxui-ink" variants={{ rest: { scaleX: 0 }, on: { scaleX: 1 } }} transition={{ duration: 0.35, ease: curves.glide }} />
      </p>
      <div className="mt-2 text-sm text-gxui-muted">{children}</div>
      {href && (
        <motion.span aria-hidden className="mt-4 inline-flex items-center gap-1 font-mono text-xs" variants={{ rest: { opacity: 0.5, x: 0 }, on: { opacity: 1, x: 4 } }}>
          Learn more →
        </motion.span>
      )}
    </Tag>
  );
}

Props

PropTypeDefaultDescription
iconReactNode—24px icon.
titlestring—Title.
childrenReactNode—Text.
hrefstring—Makes the whole card one link.
indexstring—Step number.
classNamestring—Extra classes for the outer element.

Variants

  • Steps. With an index like 01.

Accessibility

  • One focusable link with href.

Motion and reduced motion

  • Spring variants on hover and focus.
  • Reduced motion: still.