Zoeken...  ⌘K GitHub

IconGrid icon

Grid van icoon + titel + beschrijving. 4 icoon stijlen (plain, circle, square, outlined). Staggered fade-in.

/icon-grid
src/components/icon/IconGrid.astro
---
interface Props {
  eyebrow?: string;
  headline?: string;
  sub?: string;
  items: {
    icon: string;
    title: string;
    description: string;
    href?: string;
  }[];
  columns?: 2 | 3 | 4;
  iconStyle?: 'plain' | 'circle' | 'square' | 'outlined';
  size?: 'sm' | 'md' | 'lg';
}

const {
  eyebrow,
  headline,
  sub,
  items = [],
  columns = 3,
  iconStyle = 'circle',
  size = 'md',
} = Astro.props;
---

<section class={`igr__section igr__cols-${columns} igr__size-${size} igr__style-${iconStyle}`}>
  {(eyebrow || headline || sub) && (
    <div class="igr__header">
      {eyebrow && <p class="igr__eyebrow">{eyebrow}</p>}
      {headline && <h2 class="igr__headline">{headline}</h2>}
      {sub && <p class="igr__sub">{sub}</p>}
    </div>
  )}

  <ul class="igr__grid" role="list">
    {items.map((item, i) => (
      <li class="igr__item" style={`--delay: ${i * 80}ms`}>
        {item.href ? (
          <a href={item.href} class="igr__card igr__card--link">
            <span class="igr__icon-wrap" aria-hidden="true" set:html={item.icon} />
            <span class="igr__title">{item.title}</span>
            {item.description && <span class="igr__desc">{item.description}</span>}
          </a>
        ) : (
          <div class="igr__card">
            <span class="igr__icon-wrap" aria-hidden="true" set:html={item.icon} />
            <span class="igr__title">{item.title}</span>
            {item.description && <span class="igr__desc">{item.description}</span>}
          </div>
        )}
      </li>
    ))}
  </ul>
</section>

<script>
  const grids = document.querySelectorAll<HTMLElement>('.igr__grid');

  grids.forEach((grid) => {
    const items = grid.querySelectorAll<HTMLElement>('.igr__item');

    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            (entry.target as HTMLElement).classList.add('igr__item--visible');
            observer.unobserve(entry.target);
          }
        });
      },
      { threshold: 0.1, rootMargin: '0px 0px -40px 0px' }
    );

    items.forEach((item) => observer.observe(item));
  });
</script>

<style>
  :root {
    --color-primary: #0a0a0a;
    --color-accent: #6366f1;
    --color-bg: #fff;
    --color-muted: #6b7280;
    --radius: 0.5rem;
  }

  .igr__section {
    padding: 5rem 1.5rem;
    max-width: 72rem;
    margin-inline: auto;
  }

  /* Header */
  .igr__header {
    text-align: center;
    margin-bottom: 3rem;
  }

  .igr__eyebrow {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-accent);
    margin: 0 0 0.75rem;
  }

  .igr__headline {
    font-size: clamp(1.75rem, 3vw, 2.5rem);
    font-weight: 800;
    color: var(--color-primary);
    margin: 0 0 1rem;
    line-height: 1.15;
  }

  .igr__sub {
    font-size: 1.0625rem;
    color: var(--color-muted);
    max-width: 40rem;
    margin-inline: auto;
    line-height: 1.65;
    margin-top: 0;
    margin-bottom: 0;
  }

  /* Grid */
  .igr__grid {
    display: grid;
    gap: 1.5rem;
    list-style: none;
    padding: 0;
    margin: 0;
  }

  .igr__cols-2 .igr__grid { grid-template-columns: repeat(2, 1fr); }
  .igr__cols-3 .igr__grid { grid-template-columns: repeat(3, 1fr); }
  .igr__cols-4 .igr__grid { grid-template-columns: repeat(4, 1fr); }

  /* Card */
  .igr__card {
    display: flex;
    flex-direction: column;
    gap: 0.875rem;
    padding: 1.5rem;
    border-radius: var(--radius);
    border: 1px solid rgba(0, 0, 0, 0.07);
    background: var(--color-bg);
    height: 100%;
    transition: transform 0.22s ease, box-shadow 0.22s ease;
  }

  .igr__card--link {
    text-decoration: none;
    color: inherit;
  }

  .igr__card:hover {
    transform: translateY(-3px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
  }

  /* Icon wrapper */
  .igr__icon-wrap {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    color: var(--color-accent);
  }

  /* Size: md (default) */
  .igr__size-md .igr__icon-wrap { width: 48px; height: 48px; }
  .igr__size-sm .igr__icon-wrap { width: 40px; height: 40px; }
  .igr__size-lg .igr__icon-wrap { width: 64px; height: 64px; }

  .igr__icon-wrap :global(svg) {
    width: 55%;
    height: 55%;
  }

  /* iconStyle: plain */
  .igr__style-plain .igr__icon-wrap {
    background: none;
    border: none;
  }

  /* iconStyle: circle */
  .igr__style-circle .igr__icon-wrap {
    border-radius: 50%;
    background: rgba(99, 102, 241, 0.08);
  }

  /* iconStyle: square */
  .igr__style-square .igr__icon-wrap {
    border-radius: var(--radius);
    background: rgba(99, 102, 241, 0.08);
  }

  /* iconStyle: outlined */
  .igr__style-outlined .igr__icon-wrap {
    border-radius: var(--radius);
    border: 1.5px solid rgba(99, 102, 241, 0.35);
    background: transparent;
  }

  /* Text */
  .igr__title {
    font-size: 1rem;
    font-weight: 700;
    color: var(--color-primary);
    line-height: 1.3;
  }

  .igr__desc {
    font-size: 0.9375rem;
    color: var(--color-muted);
    line-height: 1.65;
  }

  /* Animation */
  .igr__item {
    opacity: 0;
    transform: translateY(20px);
  }

  .igr__item--visible {
    animation: igr-fadein 0.5s ease forwards;
    animation-delay: var(--delay, 0ms);
  }

  @keyframes igr-fadein {
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }

  /* Responsive */
  @media (max-width: 900px) {
    .igr__cols-4 .igr__grid { grid-template-columns: repeat(2, 1fr); }
  }

  @media (max-width: 600px) {
    .igr__cols-2 .igr__grid,
    .igr__cols-3 .igr__grid,
    .igr__cols-4 .igr__grid {
      grid-template-columns: 1fr;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    * { animation: none !important; transition: none !important; }
  }
</style>

Props

Prop Type Default Beschrijving
items * { icon: string; title: string; description: string; href?: string }[] Grid items
columns 2 | 3 | 4 3 Aantal kolommen
iconStyle 'plain' | 'circle' | 'square' | 'outlined' 'circle' Icoon achtergrond stijl
size 'sm' | 'md' | 'lg' 'md' Icoon grootte

* = verplicht