Zoeken...  ⌘K GitHub

FeaturesGrid Sections

Feature kaarten grid met icon, titel en beschrijving. 3 visuele varianten.

/features-grid
src/components/sections/FeaturesGrid.astro
---
/**
 * FeaturesGrid
 * Feature-kaarten in een grid. Elk met icon, titel en beschrijving.
 *
 * Props:
 * - headline?: string
 * - sub?: string
 * - features: Array<{ icon?: string; title: string; body: string; href?: string }>
 * - columns?: 2 | 3 | 4
 * - variant?: 'card' | 'minimal' | 'bordered'
 */
interface Props {
  headline?: string;
  sub?: string;
  features: { icon?: string; title: string; body: string; href?: string }[];
  columns?: 2 | 3 | 4;
  variant?: 'card' | 'minimal' | 'bordered';
}

const { headline, sub, features, columns = 3, variant = 'card' } = Astro.props;
---

<section class={`features-grid features-grid--${variant} features-grid--cols-${columns}`} data-features>
  <div class="features-grid__inner">
    {(headline || sub) && (
      <div class="features-grid__header">
        {headline && <h2 class="features-grid__headline">{headline}</h2>}
        {sub && <p class="features-grid__sub">{sub}</p>}
      </div>
    )}
    <ul class="features-grid__list">
      {features.map(f => (
        <li class="features-grid__item">
          {f.href ? (
            <a href={f.href} class="features-grid__card">
              {f.icon && <span class="features-grid__icon" aria-hidden="true">{f.icon}</span>}
              <h3 class="features-grid__title">{f.title}</h3>
              <p class="features-grid__body">{f.body}</p>
            </a>
          ) : (
            <div class="features-grid__card">
              {f.icon && <span class="features-grid__icon" aria-hidden="true">{f.icon}</span>}
              <h3 class="features-grid__title">{f.title}</h3>
              <p class="features-grid__body">{f.body}</p>
            </div>
          )}
        </li>
      ))}
    </ul>
  </div>
</section>

<style>
  .features-grid {
    padding: 5rem 1.5rem;
    background: var(--color-bg);
  }
  .features-grid__inner { max-width: 1280px; margin: 0 auto; }
  .features-grid__header {
    text-align: center;
    max-width: 640px;
    margin: 0 auto 3.5rem;
  }
  .features-grid__headline {
    font-size: clamp(1.75rem, 3vw, 2.5rem);
    font-weight: 800;
    letter-spacing: -0.03em;
    margin: 0 0 0.75rem;
  }
  .features-grid__sub {
    font-size: 1.0625rem;
    color: var(--color-muted);
    line-height: 1.6;
    margin: 0;
  }
  .features-grid__list {
    list-style: none;
    margin: 0; padding: 0;
    display: grid;
    gap: 1.5rem;
    grid-template-columns: 1fr;
  }
  @media (min-width: 600px) {
    .features-grid--cols-2 .features-grid__list,
    .features-grid--cols-3 .features-grid__list,
    .features-grid--cols-4 .features-grid__list { grid-template-columns: 1fr 1fr; }
  }
  @media (min-width: 960px) {
    .features-grid--cols-3 .features-grid__list { grid-template-columns: repeat(3, 1fr); }
    .features-grid--cols-4 .features-grid__list { grid-template-columns: repeat(4, 1fr); }
  }

  /* Card variant */
  .features-grid--card .features-grid__card {
    display: block;
    padding: 1.75rem;
    background: color-mix(in srgb, var(--color-text) 4%, transparent);
    border-radius: calc(var(--radius) * 2);
    text-decoration: none;
    color: inherit;
    transition: transform 0.2s, box-shadow 0.2s;
    height: 100%;
  }
  .features-grid--card .features-grid__card:hover {
    transform: translateY(-3px);
    box-shadow: 0 12px 40px -8px rgba(0,0,0,0.12);
  }

  /* Bordered variant */
  .features-grid--bordered .features-grid__card {
    display: block;
    padding: 1.75rem;
    border: 1px solid color-mix(in srgb, var(--color-text) 12%, transparent);
    border-radius: calc(var(--radius) * 2);
    text-decoration: none;
    color: inherit;
    height: 100%;
  }

  /* Minimal variant — geen achtergrond */
  .features-grid--minimal .features-grid__card {
    display: block;
    padding: 0.5rem 0;
    text-decoration: none;
    color: inherit;
  }

  .features-grid__icon {
    display: block;
    font-size: 1.75rem;
    margin-bottom: 1rem;
  }
  .features-grid__title {
    font-size: 1.0625rem;
    font-weight: 700;
    margin: 0 0 0.5rem;
  }
  .features-grid__body {
    font-size: 0.9375rem;
    color: var(--color-muted);
    line-height: 1.6;
    margin: 0;
  }
</style>

<script>
  if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
    gsap.registerPlugin(ScrollTrigger);
    gsap.from('[data-features] .features-grid__item', {
      y: 20, opacity: 0, duration: 0.5, stagger: 0.07, ease: 'power2.out',
      scrollTrigger: { trigger: '[data-features]', start: 'top 75%' }
    });
  }
</script>

Props

Prop Type Default Beschrijving
features * { icon?: string; title: string; body: string; href?: string }[] Feature items
headline string Sectie headline
sub string Ondertitel
columns 2 | 3 | 4 3 Aantal kolommen
variant 'card' | 'minimal' | 'bordered' 'card' Visuele stijl

* = verplicht