SectionsSimpleApril 9, 2026

FAQ Section

Two-column FAQ layout with a sticky heading on the left and an accordion list on the right. Expand/collapse is driven by CSS grid-template-rows animation — no JavaScript animation library needed. Items, questions, and answers are fully prop-driven.

View Full Demo →

Common
questions

You can send us any design or development request — landing pages, web apps, dashboards, components, or full product builds. If you're unsure, just reach out and we'll let you know if it's a good fit.

Most requests are completed within 2–4 business days. Larger or more complex work may take longer, but we'll always give you a clear timeline upfront.

You'll work directly with Kevin — no account managers or hand-offs. Every project gets personal attention from start to finish.

Yes. You can pause or cancel your subscription at any time. There are no long-term contracts or cancellation fees.

We don't cover print design, video production, or SEO copywriting. If you're unsure whether your request is in scope, just ask.

We'll revise until you're satisfied. Our goal is to deliver work you're proud of — if something isn't right, we'll make it right.

demo.jsx
import FAQSection from "./index.jsx";

export default function FAQSectionDemo() {
  return <FAQSection />;
}
index.jsx
"use client";

import { useState } from "react";
import Section from "@/components/primitives/Section";
import Container from "@/components/primitives/Container";
import styles from "./styles.module.css";

const FAQ_ITEMS = [
  {
    question: "What can I send you?",
    answer:
      "You can send us any design or development request — landing pages, web apps, dashboards, components, or full product builds. If you're unsure, just reach out and we'll let you know if it's a good fit.",
  },
  {
    question: "How fast will I get my work?",
    answer:
      "Most requests are completed within 2–4 business days. Larger or more complex work may take longer, but we'll always give you a clear timeline upfront.",
  },
  {
    question: "Who will I work with?",
    answer:
      "You'll work directly with Kevin — no account managers or hand-offs. Every project gets personal attention from start to finish.",
  },
  {
    question: "Can I pause or cancel?",
    answer:
      "Yes. You can pause or cancel your subscription at any time. There are no long-term contracts or cancellation fees.",
  },
  {
    question: "What's not included?",
    answer:
      "We don't cover print design, video production, or SEO copywriting. If you're unsure whether your request is in scope, just ask.",
  },
  {
    question: "What if I'm not happy with the work?",
    answer:
      "We'll revise until you're satisfied. Our goal is to deliver work you're proud of — if something isn't right, we'll make it right.",
  },
];

function PlusIcon() {
  return (
    <svg width="20" height="20" viewBox="0 0 16 16" fill="none"
      stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
      <line x1="8" y1="2" x2="8" y2="14" />
      <line x1="2" y1="8" x2="14" y2="8" />
    </svg>
  );
}

function CloseIcon() {
  return (
    <svg width="20" height="20" viewBox="0 0 16 16" fill="none"
      stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
      <line x1="2" y1="2" x2="14" y2="14" />
      <line x1="14" y1="2" x2="2" y2="14" />
    </svg>
  );
}

function FAQItem({ question, answer, isOpen, onToggle }) {
  return (
    <div className={`${styles.faqItem} ${isOpen ? styles.faqItemOpen : ""}`}>
      <button
        className={styles.faqHeader}
        onClick={onToggle}
        aria-expanded={isOpen}
      >
        <span className={styles.faqQuestion}>{question}</span>
        <span className={styles.faqIcon} aria-hidden="true">
          {isOpen ? <CloseIcon /> : <PlusIcon />}
        </span>
      </button>
      <div className={styles.faqBodyWrapper}>
        <div className={styles.faqBodyInner}>
          <p className={styles.faqAnswer}>{answer}</p>
        </div>
      </div>
    </div>
  );
}

export default function FAQSection({ items = FAQ_ITEMS }) {
  const [openIndex, setOpenIndex] = useState(null);

  function handleToggle(index) {
    setOpenIndex((prev) => (prev === index ? null : index));
  }

  return (
    <Section className={styles.section} variant="bare">
      <Container>
        <div className={styles.faqInner}>
          <div className={styles.faqLeft}>
            <h2 className={styles.heading}>
              Common<br />questions
            </h2>
          </div>
          <div className={styles.faqList}>
            {items.map((item, index) => (
              <FAQItem
                key={index}
                question={item.question}
                answer={item.answer}
                isOpen={openIndex === index}
                onToggle={() => handleToggle(index)}
              />
            ))}
          </div>
        </div>
      </Container>
    </Section>
  );
}
styles.module.css
/* Local tokens — component is self-contained */
.section {
  --color-white: #ffffff;
  --color-gray: rgba(255, 255, 255, 0.45);
  --space-xl:   1.5rem;
  --space-2xl:  2.5rem;
  --space-4xl:  3.5rem;
  --space-6xl:  4.5rem;
  --space-10xl: 7rem;
  --font-body:  1rem;

  background-color: #1f1f20;
  padding-block: var(--space-10xl);
}

.faqInner {
  display: flex;
  flex-direction: row;
  gap: var(--space-xl);
  align-items: flex-start;
}

.faqLeft {
  flex-shrink: 0;
  width: 35.9375rem;
}

.heading {
  font-size: clamp(2.25rem, 5vw, 4.25rem);
  font-weight: 500;
  color: var(--color-white);
  line-height: 0.95;
  letter-spacing: -0.02em;
  font-family: Arial, Helvetica, sans-serif;
}

.faqList {
  flex: 1;
  display: flex;
  flex-direction: column;
}

.faqItem {
  border-bottom: 1px solid rgba(255, 255, 255, 0.15);
}

.faqItem:first-child {
  border-top: 1px solid rgba(255, 255, 255, 0.15);
}

.faqHeader {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding-block: var(--space-xl);
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
  gap: var(--space-2xl);
  outline: none;
}

.faqHeader:focus-visible {
  outline: 2px solid var(--color-white);
  outline-offset: 2px;
}

.faqQuestion {
  font-size: 1rem;
  font-weight: var(--weight-medium);
  color: var(--color-gray);
  line-height: 1.4;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-family: var(--font-mono);
  transition: color 200ms ease;
}

.faqItemOpen .faqQuestion,
.faqHeader:hover .faqQuestion {
  color: var(--color-white);
}

.faqIcon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--color-gray);
  transition: color 200ms ease;
}

.faqItemOpen .faqIcon,
.faqHeader:hover .faqIcon {
  color: var(--color-white);
}

/* CSS grid accordion — no JS needed for the animation */
.faqBodyWrapper {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 400ms cubic-bezier(0.625, 0.05, 0, 1);
}

.faqItemOpen .faqBodyWrapper {
  grid-template-rows: 1fr;
}

.faqBodyInner {
  overflow: hidden;
}

.faqAnswer {
  padding-bottom: var(--space-xl);
  font-size: var(--font-body);
  font-weight: var(--weight-normal);
  color: rgba(255, 255, 255, 0.6);
  line-height: 1.6;
  font-family: var(--font-mono);
  max-width: 40rem;
}

@media (max-width: 991px) {
  .faqInner {
    gap: var(--space-6xl);
  }

  .faqLeft {
    width: 14rem;
  }
}

@media (max-width: 479px) {
  .section {
    padding-block: var(--space-4xl);
  }

  .faqInner {
    flex-direction: column;
    gap: var(--space-4xl);
  }

  .faqLeft {
    width: 100%;
  }
}

May 11, 2026

SECTIONS

Dual Push Cards

Two-up CTA card section with scroll-driven parallax on each card's background image. Cards scale down and drift vertically as you scroll past. Glassmorphic blur buttons at bottom-left. Stacks on mobile, side-by-side grid on desktop.

May 4, 2026

SECTIONS

Portfolio Grid

A responsive portfolio showcase section with a header tagline, blinking cursor counters, a 2-up/4-col project card grid with hover-zoom images and data-label metadata, plus a full-width CTA button. Scroll-triggered fade and move-up animations via GSAP.

May 1, 2026

SECTIONS

Logo Wall Cycle

A responsive logo grid that cycles through brand logos with smooth GSAP-powered swap animations. Shows 8 logos on desktop and 6 on tablet, shuffling hidden logos into view on a timed loop. Pauses when out of viewport or tab is hidden.