---
title: "Features"
description: "A responsive feature grid built from cards."
---

Interactive preview: https://ui.coderocket.app/docs/blocks/features

## Usage

Get this block from a [saved library export](/docs/export) or the [connected CLI](/docs/agents). The imports below refer to local files installed in your app.

Import `styles/components.css`, then `styles/blocks.css`, then `styles/theme.css` once. Wrap your app in `ThemeScope` from `components/ui/utils` so typography, focus and overlays share the theme. The [installation guide](/docs/export) covers runtime dependencies and React/Next.js setup.

This import is the entry point, not a complete integration example. Supply the required props and connect callbacks to your application. The full ZIP includes `examples/blocks-preview.tsx` with the populated preview and local sample callbacks; adapt those examples to your product.

```tsx
import { FeaturesBlock } from './components/blocks/marketing';

// See Source and props below for the complete typed interface.
```

## Design system

This block consumes your semantic colors, spacing, typography and shape tokens. Import the compiled styles and your theme once as described in [Export](/docs/export).

Icons use Lucide React. Install `lucide-react@1.47.0` alongside React and Base UI.

Dependencies: [Accordion](/docs/components/accordion), [Avatar](/docs/components/avatar), [Badge](/docs/components/badge), [Card](/docs/components/card).

## Behavior and accessibility

- Provide meaningful labels for interactive controls.
- Connect action callbacks to your application logic.
- Verify keyboard navigation and contrast in your application.

Status: **experimental** during early access. Sample previews do not send emails, process payments or upload files to a service.

## Source and props

This is the exported source, using the same implementation as the editor. The complete source group is included below; related blocks share it to avoid duplicating behavior.

```tsx
"use client";
import type { ReactNode } from "react";
import {
  ArrowRight,
  ArrowUpRight,
  Check,
  Layers3,
  Quote,
  SlidersHorizontal,
  Workflow,
} from "lucide-react";
import { Accordion, Avatar, Badge, Card } from '../ui';

function MarketingHeading({
  title,
  description,
  eyebrow,
}: {
  title: string;
  description?: string;
  eyebrow?: string;
}) {
  return (
    <header className="cr-marketing-heading">
      <div>
        {eyebrow && <p className="cr-marketing-eyebrow">{eyebrow}</p>}
        <h2>{title}</h2>
      </div>
      {description && <p className="cr-description">{description}</p>}
    </header>
  );
}

export function HeroBlock({
  eyebrow,
  title,
  description,
  primary,
  secondary,
  children,
}: {
  eyebrow: string;
  title: string;
  description: string;
  primary: { label: string; href: string };
  secondary?: { label: string; href: string };
  children?: ReactNode;
}) {
  return (
    <section className="cr-hero cr-marketing-hero">
      <div className="cr-hero-decoration" aria-hidden="true">
        <span>
          <Layers3 strokeWidth={1.75} />
        </span>
        <span>
          <SlidersHorizontal strokeWidth={1.75} />
        </span>
        <span>
          <Workflow strokeWidth={1.75} />
        </span>
      </div>
      <div className="cr-hero-content">
        <Badge variant="outline">
          {eyebrow}
          <ArrowUpRight size={13} strokeWidth={1.75} aria-hidden="true" />
        </Badge>
        <h1>{title}</h1>
        <p className="cr-description">{description}</p>
        <div className="cr-hero-actions">
          <a className="cr-button" href={primary.href}>
            {primary.label}
            <ArrowRight size={17} strokeWidth={1.75} aria-hidden="true" />
          </a>
          {secondary && (
            <a className="cr-button" data-variant="ghost" href={secondary.href}>
              {secondary.label}
              <ArrowUpRight size={16} strokeWidth={1.75} aria-hidden="true" />
            </a>
          )}
        </div>
      </div>
      {children && <div className="cr-hero-media">{children}</div>}
    </section>
  );
}

export function FeaturesBlock({
  title,
  description,
  eyebrow,
  items,
}: {
  title: string;
  description: string;
  eyebrow?: string;
  items: Array<{
    title: string;
    description: string;
    icon?: ReactNode;
    visual?: ReactNode;
    eyebrow?: string;
    href?: string;
    linkLabel?: string;
  }>;
}) {
  const fallbackIcons = [Layers3, Workflow, SlidersHorizontal];
  return (
    <section className="cr-marketing-section cr-features-section">
      <MarketingHeading
        title={title}
        description={description}
        eyebrow={eyebrow}
      />
      <div className="cr-features-grid">
        {items.map((item, index) => {
          const Icon = fallbackIcons[index % fallbackIcons.length];
          return (
            <Card
              key={item.title}
              className="cr-feature-card"
              data-featured={(index === 0 && items.length >= 3) || undefined}
              data-has-visual={Boolean(item.visual) || undefined}
            >
              <div className="cr-feature-copy">
                <div className="cr-feature-topline">
                  <span aria-hidden="true" className="cr-feature-icon">
                    {item.icon ?? <Icon size={22} strokeWidth={1.75} />}
                  </span>
                  <span className="cr-feature-index" aria-hidden="true">
                    {String(index + 1).padStart(2, "0")}
                  </span>
                </div>
                {item.eyebrow && (
                  <p className="cr-marketing-eyebrow">{item.eyebrow}</p>
                )}
                <h3>{item.title}</h3>
                <p className="cr-description">{item.description}</p>
                {item.href && (
                  <a className="cr-feature-link" href={item.href}>
                    {item.linkLabel ?? "Explore feature"}
                    <ArrowUpRight
                      size={16}
                      strokeWidth={1.75}
                      aria-hidden="true"
                    />
                  </a>
                )}
              </div>
              {item.visual && (
                <div className="cr-feature-visual">{item.visual}</div>
              )}
            </Card>
          );
        })}
      </div>
    </section>
  );
}

export function PricingBlock({
  title,
  plans,
  eyebrow,
  description,
}: {
  title: string;
  eyebrow?: string;
  description?: string;
  plans: Array<{
    name: string;
    price: string;
    period: string;
    description: string;
    features: string[];
    href: string;
    action: string;
    featured?: boolean;
  }>;
}) {
  return (
    <section className="cr-marketing-section cr-pricing-section">
      <MarketingHeading
        title={title}
        description={description}
        eyebrow={eyebrow}
      />
      <div className="cr-pricing-grid">
        {plans.map((plan) => (
          <Card
            key={plan.name}
            className="cr-pricing-card"
            data-featured={plan.featured || undefined}
          >
            <div className="cr-plan-heading">
              <h3>{plan.name}</h3>
              <div
                className="cr-price-recommendation"
                aria-hidden={!plan.featured || undefined}
              >
                {plan.featured && <Badge variant="primary">Recommended</Badge>}
              </div>
            </div>
            <p className="cr-plan-description cr-description">
              {plan.description}
            </p>
            <p className="cr-price">
              {plan.price}
              <small>{plan.period}</small>
            </p>
            <ul className="cr-price-features">
              {plan.features.map((feature) => (
                <li key={feature}>
                  <Check size={17} strokeWidth={1.75} aria-hidden="true" />
                  <span>{feature}</span>
                </li>
              ))}
            </ul>
            <a
              className="cr-button"
              data-variant={plan.featured ? "primary" : "outline"}
              href={plan.href}
            >
              {plan.action}
              <ArrowRight size={16} strokeWidth={1.75} aria-hidden="true" />
            </a>
          </Card>
        ))}
      </div>
    </section>
  );
}

export function TestimonialsBlock({
  title,
  quotes,
  eyebrow,
  description,
}: {
  title: string;
  eyebrow?: string;
  description?: string;
  quotes: Array<{ quote: string; name: string; role: string; avatar?: string }>;
}) {
  return (
    <section className="cr-marketing-section cr-testimonials-section">
      <MarketingHeading
        title={title}
        description={description}
        eyebrow={eyebrow}
      />
      <div className="cr-testimonials-grid">
        {quotes.map((quote, index) => (
          <Card
            key={quote.name}
            className="cr-testimonial-card"
            data-featured={index === 0 || undefined}
          >
            <figure className="cr-testimonial-figure">
              <Quote
                className="cr-quote-icon"
                size={26}
                strokeWidth={1.75}
                aria-hidden="true"
              />
              <blockquote className="cr-quote">
                <p>{quote.quote}</p>
              </blockquote>
              <figcaption className="cr-quote-author">
                <Avatar name={quote.name} src={quote.avatar} size={40} />
                <div>
                  <cite>{quote.name}</cite>
                  <p className="cr-description">{quote.role}</p>
                </div>
              </figcaption>
            </figure>
          </Card>
        ))}
      </div>
    </section>
  );
}

export function FaqBlock({
  title,
  items,
  eyebrow,
  description,
  contact,
}: {
  title: string;
  eyebrow?: string;
  description?: string;
  contact?: { label: string; href: string };
  items: Array<{ question: string; answer: ReactNode }>;
}) {
  return (
    <section className="cr-marketing-section cr-faq-section">
      <div className="cr-faq-layout">
        <div className="cr-faq-intro">
          <MarketingHeading
            title={title}
            description={description}
            eyebrow={eyebrow}
          />
          {contact && (
            <a className="cr-feature-link" href={contact.href}>
              {contact.label}
              <ArrowUpRight size={16} strokeWidth={1.75} aria-hidden="true" />
            </a>
          )}
        </div>
        <div className="cr-faq-content">
          <Accordion
            items={items.map((item, index) => ({
              value: String(index),
              title: item.question,
              content: item.answer,
            }))}
          />
        </div>
      </div>
    </section>
  );
}

export function CtaBlock({
  title,
  description,
  action,
  eyebrow,
  secondary,
}: {
  title: string;
  description: string;
  action: { label: string; href: string };
  eyebrow?: string;
  secondary?: { label: string; href: string };
}) {
  return (
    <Card className="cr-cta-card">
      <div className="cr-cta">
        <div className="cr-cta-content">
          {eyebrow && <p className="cr-marketing-eyebrow">{eyebrow}</p>}
          <h2>{title}</h2>
          <p className="cr-description">{description}</p>
          <div className="cr-cta-actions">
            <a className="cr-button" href={action.href}>
              {action.label}
              <ArrowRight size={17} strokeWidth={1.75} aria-hidden="true" />
            </a>
            {secondary && (
              <a className="cr-feature-link" href={secondary.href}>
                {secondary.label}
                <ArrowUpRight size={16} strokeWidth={1.75} aria-hidden="true" />
              </a>
            )}
          </div>
        </div>
        <div className="cr-cta-emblem" aria-hidden="true">
          <ArrowUpRight strokeWidth={1.75} />
        </div>
      </div>
    </Card>
  );
}

export function FooterBlock({
  brand,
  description,
  groups,
  copyright,
  logo,
  brandHref,
  legalLinks,
}: {
  brand: string;
  description: string;
  groups: Array<{
    title: string;
    links: Array<{ label: string; href: string }>;
  }>;
  copyright: string;
  logo?: ReactNode;
  brandHref?: string;
  legalLinks?: Array<{ label: string; href: string }>;
}) {
  const identity = (
    <>
      {logo && (
        <span className="cr-footer-logo" aria-hidden="true">
          {logo}
        </span>
      )}
      <strong>{brand}</strong>
    </>
  );
  return (
    <footer className="cr-footer">
      <div className="cr-footer-main">
        <div className="cr-footer-brand">
          {brandHref ? (
            <a className="cr-footer-identity" href={brandHref}>
              {identity}
            </a>
          ) : (
            <div className="cr-footer-identity">{identity}</div>
          )}
          <p className="cr-description">{description}</p>
        </div>
        <div className="cr-footer-navigation">
          {groups.map((group) => (
            <nav key={group.title} aria-label={group.title}>
              <strong>{group.title}</strong>
              {group.links.map((link) => (
                <a key={link.href} href={link.href}>
                  {link.label}
                </a>
              ))}
            </nav>
          ))}
        </div>
      </div>
      <div className="cr-footer-bottom">
        <p className="cr-description">{copyright}</p>
        {legalLinks?.length ? (
          <nav aria-label="Legal">
            {legalLinks.map((link) => (
              <a href={link.href} key={link.href}>
                {link.label}
              </a>
            ))}
          </nav>
        ) : null}
      </div>
    </footer>
  );
}
```
