SOYOYU
블로그로 돌아가기기술 SEO

FAQ 아코디언에 JavaScript는 필요 없습니다 — details/summary 코드 리뷰

FAQ 아코디언을 만들 때 JavaScript와 상태 관리부터 떠올렸다면 — native <details>/<summary> 두 태그가 그 모든 일을 하고 a11y까지 무료로 줍니다. SEO·AI 인용에 미치는 영향도 다릅니다.

전승엽2026년 6월 27일9 min read
A11y 코드 리뷰시맨틱 HTMLaccessibility treeAI 에이전트웹 접근성details 태그summary 태그FAQ 아코디언KWCAG

A11y 코드 리뷰 #004 · 오늘 도감에 등록할 태그 — <details><summary>

이번 도감 카드는 시리즈 중 가장 짧고 가장 강력한 한 페어입니다. 두 태그로 JavaScript 0줄짜리 아코디언이 만들어지고, 그게 곧 가장 a11y가 좋은 아코디언이며, AI Overviews 인용 신호로도 가장 강한 아코디언입니다.

TL;DR

  • <details><summary> 두 태그만 있으면 JavaScript 없이 아코디언이 완성됩니다. open 속성, toggle 이벤트, ESC 닫기까지 모두 무료입니다.
  • 2024년부터 <details name="group">이 지원되면서 단일 오픈(exclusive) 아코디언도 native로 가능합니다.
  • 스크린리더는 details를 "확장 가능, 접힘/펼침"으로 읽습니다. JS 아코디언은 aria-expanded를 직접 동기화해야 그 동작을 흉내 낼 수 있습니다.
  • FAQ 페이지가 native <details> 기반이면 — 검색엔진과 AI Overviews가 콘텐츠 추출에 더 친화적입니다. 닫혀 있어도 콘텐츠가 DOM에 명시적으로 존재합니다.
  • AI 에이전트가 아코디언을 펼쳐서 정보를 찾는 작업에서 — native details는 트리에서 disclosure triangle로 명시되어 인식 성공률이 높습니다.

1. 어디서나 본 그 아코디언

다음 세 패턴은 거의 모든 사이트에 있습니다.

JS 상태 + div로 만든 FAQ

function FAQItem({ q, a }: Props) {
  const [open, setOpen] = useState(false);
  return (
    <div className="faq-row">
      <div onClick={() => setOpen(!open)} className="faq-question">
        <span>{q}</span>
        <span>{open ? "−" : "+"}</span>
      </div>
      {open && <div className="faq-answer">{a}</div>}
    </div>
  );
}

라이브러리 기반 아코디언

import { Accordion } from "some-ui-library";

<Accordion items={items} allowMultiple={false} />

"더 보기" 토글로 본문 일부를 숨기기

<p>
  {expanded ? fullText : fullText.slice(0, 200) + "..."}
  <button onClick={() => setExpanded(!expanded)}>
    {expanded ? "접기" : "더 보기"}
  </button>
</p>

세 패턴 모두 시각적으로는 동작합니다. 그런데 각각이 다음을 직접 처리해야 합니다 — 키보드 포커스, Enter/Space 동작, aria-expanded 상태 동기화, 펼침/접힘 상태의 스크린리더 알림, 닫힐 때의 포커스 처리. 네 라인이 빠지면 a11y가 깨집니다.


2. 한 가지 질문

사용자가 펼침/접힘 상태를 명확하게 알 수 있을까요? 시각·청각·자동화 도구 모두가 동시에?

답은 — 그 상태를 브라우저가 자동으로 관리하게 두면 된다입니다. 그게 native <details>/<summary>가 하는 일입니다.


3. details/summary — 점진적 해부

3.1 가장 단순한 details

<details>
  <summary>주문 후 며칠 안에 배송되나요?</summary>
  <p>평일 기준 1-2일 안에 출고, 3-5일 안에 도착합니다.</p>
</details>

이게 전부입니다. JavaScript 0줄. 클릭하면 펼침/접힘, Enter/Space로 발화, Tab으로 포커스, 스크린리더가 "확장 가능, 접힘"으로 읽습니다.

3.2 기본값 펼침 — open 속성

<details open>
  <summary>가장 중요한 답변</summary>
  <p>이 항목은 페이지 진입 시 펼쳐진 상태로 시작합니다.</p>
</details>

open 속성으로 초기 상태 제어. React에서는 controlled로도 uncontrolled로도 쓸 수 있습니다.

3.3 단일 오픈(exclusive) 아코디언 — name 속성

2024년부터 모든 모던 브라우저가 <details name="..."> 그룹을 지원합니다.

<details name="faq">
  <summary>주문 후 며칠 안에 배송되나요?</summary>
  <p>평일 기준 1-2일 안에 출고됩니다.</p>
</details>

<details name="faq">
  <summary>반품은 어떻게 하나요?</summary>
  <p>고객센터 또는 마이페이지에서 신청 가능합니다.</p>
</details>

<details name="faq">
  <summary>해외 배송도 되나요?</summary>
  <p>2026년 기준 일본·미국·동남아 일부 국가 지원.</p>
</details>

같은 name을 공유하는 details들은 자동으로 single-open 동작입니다 — 하나를 열면 다른 게 자동으로 닫힙니다. 라디오 그룹의 details 버전입니다. JavaScript로 만든 라이브러리 아코디언이 하던 일을 HTML 한 속성이 대체합니다.

3.4 toggle 이벤트

펼침/접힘 시점에 처리할 일이 있다면:

<details
  onToggle={(e) => {
    if (e.currentTarget.open) {
      analytics.track("faq_opened", { question: q });
    }
  }}
>
  <summary>{q}</summary>
  <p>{a}</p>
</details>

onToggle은 React 18+에서 안정적으로 동작하는 native 이벤트입니다.

3.5 summary 스타일링

summary {
  cursor: pointer;
  list-style: none; /* 기본 삼각형 제거 */
}

summary::-webkit-details-marker {
  display: none; /* Safari */
}

summary::before {
  content: "+";
  margin-right: 0.5em;
}

details[open] summary::before {
  content: "−";
}

기본 ▶ 삼각형은 제거 가능합니다. 다만 시각적 토글 표시 자체는 반드시 있어야 합니다 — 그래야 사용자가 클릭 가능함을 인지합니다.


4. 왜 문제인가 — 3중 영향

4a. 스크린리더 사용자

native details의 발화:

<details>
  <summary>주문 후 며칠 안에 배송되나요?</summary>
  <p>평일 기준 1-2일...</p>
</details>

→ NVDA: "주문 후 며칠 안에 배송되나요?, 버튼, 접힘"
→ 사용자가 Enter 누름
→ NVDA: "확장됨" (자동 발화)

JS 아코디언으로 같은 동작을 흉내 내려면 aria-expanded 속성을 상태와 동기화해야 합니다.

<button
  aria-expanded={open}
  aria-controls={`answer-${id}`}
  onClick={() => setOpen(!open)}
>
  {q}
</button>
<div id={`answer-${id}`} hidden={!open}>
  {a}
</div>

이 코드가 동작하긴 합니다. 그런데 aria-controls를 빠뜨리는 경우가 90%이고, hidden 대신 display: none을 쓰면 트랜지션이 안 되고 등등 — 작은 실수가 누적됩니다. native details는 이 모든 걸 자동입니다.

4b. 키보드 사용자

native details는:

Tab → summary에 포커스
Enter/Space → 펼침/접힘
Tab → 펼쳐진 콘텐츠로 (링크/버튼이 있다면)

JS 아코디언은 보통 Tab → 토글 버튼 → Enter까지만 처리하고, 펼쳐진 콘텐츠 안의 인터랙티브 요소로의 Tab 이동을 빠뜨립니다. 펼쳐졌는데 포커스가 콘텐츠에 못 들어가면 사용자는 "내가 펼친 게 맞나?"를 의심합니다.

4c. AI 에이전트

AI 에이전트가 "이 사이트의 환불 정책을 알려줘" 같은 작업을 수행할 때 — FAQ 아코디언 안에 답이 있는 경우가 많습니다.

native details는 accessibility tree에서 details 또는 disclosure triangle 역할로 명시됩니다. 에이전트는 다음과 같이 동작합니다.

1. 페이지에서 "환불"이라는 키워드를 가진 disclosure를 찾음
2. 그 details의 summary를 클릭 (Enter/Space 시뮬레이션)
3. 펼쳐진 콘텐츠를 추출

3단계 모두 명확합니다. JS 아코디언이면 1번 단계에서 에이전트가 "이 div가 토글 가능한 것인지" 자체를 판단해야 합니다. cursor: pointer, ARIA expanded, 시각적 + 아이콘 등을 종합해서 추측합니다. 추측의 정확도가 native만큼 안 됩니다.

선행 글 구글이 'AI 검색 최적화 가이드'를 냈는데, 정작 핵심은 접근성 트리였습니다에서 봤듯, accessibility tree의 명확성이 곧 자동화 성공률입니다.


5. SEO/GEO/AEO 영향

여기서 details는 시리즈의 다른 어느 태그보다 직접적으로 SEO에 영향을 줍니다.

5.1 닫혀 있어도 콘텐츠는 DOM에 있습니다

JS 아코디언의 흔한 패턴 — 콘텐츠를 조건부 렌더링으로 DOM에서 빼는 것.

{open && <div className="faq-answer">{a}</div>}

이 코드는 닫힌 상태에서 콘텐츠 자체가 DOM에 없습니다. 검색엔진은 페이지를 렌더링하지만 클릭 이벤트를 실행하지는 않습니다. 결과적으로 닫힌 FAQ의 답변이 인덱싱되지 않을 가능성이 있습니다.

native <details>는 닫혀 있어도 콘텐츠가 항상 DOM에 있습니다. 단지 시각적으로 숨겨질 뿐입니다. 검색엔진과 AI 인덱서가 콘텐츠에 접근 가능합니다.

5.2 AI Overviews와 FAQ의 친화성

AI Overviews가 페이지에서 답변을 추출할 때, 가장 친화적인 구조 중 하나가 질문-답변 쌍의 명시적 마크업입니다. native details는 이 쌍을 시맨틱하게 표현합니다 — summary가 질문, 본문이 답변. 같은 페이지에서 div 아코디언으로 만든 FAQ보다 native details로 만든 FAQ가 인용 우선순위가 높은 경향이 있습니다 (직접 측정 결과는 시리즈 후속에서 정리할 예정).

5.3 FAQPage Schema와의 보완

Schema.org/FAQPage 마크업을 한 페이지가 native <details>로 구조까지 일치하면 — Schema 선언과 DOM 구조가 완전 정합입니다. 검색엔진은 이런 정합을 신뢰 신호로 봅니다.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "주문 후 며칠 안에 배송되나요?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "평일 기준 1-2일 안에 출고, 3-5일 안에 도착합니다."
      }
    }
  ]
}
</script>

<details>
  <summary>주문 후 며칠 안에 배송되나요?</summary>
  <p>평일 기준 1-2일 안에 출고, 3-5일 안에 도착합니다.</p>
</details>

Schema의 name과 details의 summary, Schema의 text와 details의 본문이 완전 일치합니다.


6. React 라이브러리에서

6.1 그냥 details 쓰기

function FAQ({ items }: { items: Array<{ q: string; a: string }> }) {
  return (
    <div>
      {items.map((item, i) => (
        <details key={i} name="faq">
          <summary>{item.q}</summary>
          <p>{item.a}</p>
        </details>
      ))}
    </div>
  );
}

이게 전부입니다. 상태 관리도, ref도, useEffect도 없습니다. name="faq"로 single-open 동작까지 자동입니다.

6.2 Radix Accordion — 추상화의 trade-off

import * as Accordion from "@radix-ui/react-accordion";

<Accordion.Root type="single" collapsible>
  <Accordion.Item value="item-1">
    <Accordion.Header>
      <Accordion.Trigger>주문 후 며칠 안에 배송되나요?</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Content>평일 기준 1-2일 안에 출고됩니다.</Accordion.Content>
  </Accordion.Item>
</Accordion.Root>

Radix Accordion이 native details보다 나은 점 — 애니메이션의 자유도, 컨트롤드 컴포넌트로 통합, 키보드 단축키(Home/End/방향키) 추가. 못한 점 — 추가 dependency, 닫힌 콘텐츠가 display: none이라 SEO에서 native details보다 약함, Schema 정합성 약화.

선택 기준 — 애니메이션과 키보드 단축키가 필요하면 Radix, 아니면 native details. FAQ 페이지는 거의 항상 native details가 답입니다.

6.3 Headless UI Disclosure

import { Disclosure } from "@headlessui/react";

<Disclosure>
  <Disclosure.Button>주문 후 며칠 안에 배송되나요?</Disclosure.Button>
  <Disclosure.Panel>평일 기준 1-2일 안에 출고됩니다.</Disclosure.Panel>
</Disclosure>

Disclosure는 단일 토글에 어울리는 컴포넌트입니다. 같은 trade-off — 추가 dependency, SEO 약화.


7. 한국 맥락 — 가볍게

KWCAG 관련 항목:

  • 4.1.2 이름, 역할, 값: details의 펼침/접힘 상태는 자동으로 보조 기술에 노출됩니다. JS 아코디언은 직접 처리해야 합니다.
  • 2.1.1 키보드 사용 보장: details는 자동으로 키보드 접근 가능합니다.

한국 사이트에 특히 많은 패턴 — 약관 페이지, 환불 정책, 배송 정책 등이 기본적으로 펼쳐진 거대한 텍스트 블록. 사용자는 ctrl+F로 찾고, AI는 페이지를 통째로 인덱싱합니다. 이 경우는 details 없이도 좋습니다. 질문 형태로 답변이 정리된 FAQ에서 details가 빛납니다.


8. "라이브러리 없이 정말 충분한가요?"

자주 듣는 반론들:

"애니메이션이 필요해요" — 2024년부터 details::details-content 의사 요소와 interpolate-size: allow-keywords로 native details에서도 부드러운 펼침 애니메이션이 가능합니다. Chrome 131+ 지원.

"브라우저 호환성은요?" — details/summary는 IE를 제외한 모든 브라우저에서 10년 넘게 지원됐습니다. name 속성도 Chrome 120+, Firefox 124+, Safari 17.4+에서 지원합니다.

"디자인 자유도가 부족해요" — summary는 일반 요소처럼 스타일링 가능합니다. flex/grid도 들어갑니다. 기본 마커만 제거하면 디자인 자유도는 div와 동일합니다.

"펼침 상태를 외부에서 제어해야 해요" — React에서 <details open={isOpen}>으로 controlled 사용 가능합니다.

"펼친 항목들 사이의 상호작용을 관리해야 해요"name 속성으로 group을 만들거나, 더 복잡하면 toggle 이벤트에서 useState로 관리 가능합니다.

결국 라이브러리 아코디언이 진짜 필요한 케이스는 — 동시 여러 개 열어두는데 키보드 단축키로 navigate하고 싶을 때 정도로 좁아집니다. FAQ는 native details로 충분합니다.


9. 체크리스트

  • FAQ 페이지가 native <details>/<summary>로 만들어졌는가
  • 단일 오픈 동작이 필요하면 <details name="..."> 그룹으로 처리했는가
  • 닫혀 있어도 콘텐츠가 DOM에 존재하는가 (조건부 렌더링이 아닌가)
  • summary의 시각적 토글 표시가 명확한가 (+/− 또는 ▶/▼)
  • FAQPage Schema가 details 구조와 일치하는가
  • NVDA/VoiceOver가 "접힘"/"펼쳐짐" 상태를 발화하는가
  • Tab → Enter/Space로 펼치고 다시 Tab으로 내부 링크에 이동 가능한가

마무리

이 글이 16편 시리즈의 네 번째 도감 카드입니다. 다음 카드는 <h1>~<h6>"h1은 한 개만"이라는 SEO 미신과 스크린리더 사용자의 실제 길찾기가 어떻게 어긋나는지 봅니다.

details/summary의 단 한 줄 규칙 — JavaScript와 상태 관리부터 떠올렸다면, 한 번 더 멈춰서 native HTML이 이미 해결한 게 아닌지 보세요. 이 시리즈에서 가장 짧게 끝나는 도감 카드인 이유 — 정답이 거의 항상 두 태그이기 때문입니다.

FAQ·약관·정책 페이지의 SEO·AEO 최적화 컨설팅이 필요하시면 문의하기로 연락 주세요.


codex:
  primary_tag: details
  secondary_tags: [summary]
  aria_roles: [group, button]
  aria_attributes: [aria-expanded, aria-controls]
  related_patterns: [disclosure, accordion, faq-page, expandable-content]
  kwcag: ["4.1.2", "2.1.1"]
  wcag: ["4.1.2", "2.1.1", "1.3.1"]
  ai_agent_impact: high
  seo_geo_impact: high
  difficulty: beginner
  test_envs:
    - "NVDA 2024.x + Chrome 125"
    - "VoiceOver macOS 14 + Safari 17"
  references:
    - "MDN, <details>: The Details disclosure element"
    - "Adrian Roselli, Brief Note on Accessible Disclosure Widgets"
    - "Schema.org, FAQPage"
    - "Chrome Developers, Exclusive accordion with the details element (2024)"

다른 글 읽기

기술 SEO

프로그래매틱 SEO: 대규모 페이지 자동 생성과 품질 통제

Google의 Scaled Content Abuse 정책 강화 속에서 프로그래매틱 SEO를 안전하게 실행하는 방법을 다룹니다. Zapier, Canva, Wise의 실제 사례와 30-40% 고유 콘텐츠 기준을 정리합니다.

7 min read
기술 SEO

Next.js layout.tsx에 main이 두 개 — landmark 코드 리뷰

main·nav·header·footer 같은 landmark 태그는 페이지의 지도입니다. 지도가 두 장이거나 없으면 스크린리더 사용자도 AI 에이전트도 길을 잃습니다. 가장 자주 잘못된 케이스부터 정리합니다.

9 min read
기술 SEO

네이버 Search Advisor 기술 감사 워크플로우: GSC와의 차이와 6단계 실전 가이드

네이버 서치어드바이저의 6개 핵심 기능을 분석하고, Google Search Console과 13개 항목을 비교합니다. 네이버에 최적화된 6단계 기술 감사 워크플로우를 제공합니다.

7 min read
기술 SEO

h1은 한 개만이라는 미신과 스크린리더 사용자의 실제 길찾기 — heading 코드 리뷰

SEO 도구는 'h1은 한 개여야 한다'고 외치지만 스크린리더 사용자는 헤딩 목록으로 페이지를 탐색합니다. 헤딩의 본질은 SEO가 아닌 목차이며, 그래서 AI Overviews 인용 신호이기도 합니다.

11 min read
기술 SEO

보안 헤더(CSP/HSTS)가 검색 순위에 미치는 영향

HTTPS는 약 12%의 순위 요인으로 확인되었지만, John Mueller는 보안 헤더가 직접적 순위 요인이 아니라고 밝혔습니다. '상위 사이트 71%가 보안 헤더 사용'이라는 통계의 검증 결과, 실제 채택률 데이터, 상관관계와 인과관계의 구분을 정리합니다.

8 min read
기술 SEO

Edge SEO 실전: 리다이렉트, 헤더 인젝션, HTML 변환 코드 예제

Cloudflare Workers로 구현하는 Edge SEO 실전 코드. 301 리다이렉트, KV 대량 리다이렉트, HSTS/CSP/X-Robots-Tag 헤더 인젝션, HTMLRewriter 메타 태그 삽입, 캐노니컬 인젝션, hreflang 인젝션, A/B 테스트까지.

8 min read

검색 최적화가 필요하신가요?

무료 상담을 통해 비즈니스에 맞는 최적화 전략을 확인하세요.