Design System
Components

FAQ

A page-end list of questions and answers in white expandable cards, with optional category chips.

Figma description · faqOpen in Figma ↗

FAQ

A page-end section for presenting common questions and answers.

Usage rules

Place at the end of the page, primarily on level-2 and level-3 pages.

Use only when there are at least 3 FAQ items. Do not use for 1–2 questions.

Tabs are optional. Add them only when the questions need categorization, and build them with Pill component instances.

Set each question box surface using the appropriate surface color from the color-token hierarchy. Do not apply arbitrary or hard-coded surface colors.

AI generation guidance

Insert an instance of this FAQ component; do not recreate its structure from scratch.

Generate page-relevant questions and concise answers, with one question per item.

When tabs are needed, group questions into clear categories and represent each category with a Pill component instance.

Preserve the component’s existing layout, spacing, states, and token bindings.

Frequently asked questions

Yes. Upgrades apply straight away; downgrades apply from your next bill.

Props
import SwiftUI
import EandTokens

// faq: optional outline-chip category row (40pt, gap 8) over a stack of white r20 cards,
// gap 4, inset 16. Header padding 16, gap 12: the question in title/xs (a 40pt block,
// 2 lines while closed) and a 20pt chevron in a 24pt box. The answer is body/md subtle,
// padded 0/12/16 inside a 0/4/4 container. One item open at a time.
struct FAQView: View {
  let items: [(q: String, a: String)]
  @State private var open: Int? = nil
  var body: some View {
    VStack(spacing: EandSpacing.xs) {
      ForEach(items.indices, id: \.self) { i in
        VStack(alignment: .leading, spacing: 0) {
          Button { withAnimation { open = open == i ? nil : i } } label: {
            HStack(spacing: EandSpacing.md) {
              Text(items[i].q)
                .font(.system(size: EandTypography.titleXs.size, weight: EandTypography.titleXs.weight))
                .lineLimit(open == i ? nil : 2)
                .frame(maxWidth: .infinity, minHeight: open == i ? nil : EandScale.scale40, alignment: .leading)
              Image(systemName: open == i ? "chevron.up" : "chevron.down")
                .font(.system(size: EandIcon.lg)).frame(width: EandIcon.xl, height: EandIcon.xl)
            }
            .foregroundColor(EandColor.textDefaultDefault)
            .padding(EandSpacing.lg)
          }
          if open == i {
            Text(items[i].a).font(.system(size: EandTypography.bodyMd.size))
              .foregroundColor(EandColor.textDefaultSubtle)
              .padding(.horizontal, EandSpacing.md).padding(.bottom, EandSpacing.lg)
              .padding([.horizontal, .bottom], EandSpacing.xs)
          }
        }
        .background(EandColor.surfaceBaseInverse)
        .clipShape(RoundedRectangle(cornerRadius: EandRadius.radius7))
      }
    }
    .padding(.horizontal, EandSpacing.lg)
  }
}

When to use

  • Use at the end of a level-2 or level-3 page for three or more questions. Add categories when the list is long enough to need filtering.
  • Don't use for one or two questions; answer them in the page content. Not for other expandable content (Accordion) or for navigation lists (ListRow).

Behaviour

  • One answer is open at a time. Opening a question closes the previous one; selecting the open question closes it. defaultOpen is the index of the item open at first; by default none is.
  • With categories, a row of outline Chips filters the list. The first chip is selected unless defaultValue says otherwise; use value with onCategoryChange to control it. An item with a category shows only under that chip; an item without one shows under every chip.
  • Each question is a button with aria-expanded, linked by aria-controls to its answer region.
  • FAQ has no fill and no heading, and it carries its own 16px side inset. Place it directly on the page canvas under a heading of its own: nesting it in a Section body adds Section's inset to its own and doubles it.
import { FAQ } from '@eand/react-design-system';

<FAQ
  categories={['Billing', 'Roaming']}
  items={[
    { question: 'When is my bill issued?', answer: 'On the same day each month.', category: 'Billing' },
    { question: 'How do I turn on roaming?', answer: 'From Manage, under Roaming.', category: 'Roaming' },
    { question: 'How do I contact support?', answer: 'Through Care, at any time.' },
  ]}
/>

Props

PropTypeDefaultDescription
itemsFAQEntry[]—
categories?string[]—Category chip labels. Omit for no chip row.
defaultOpen?number—Index into items of the item open at first. Default: none.
defaultValue?number0
onCategoryChange?((index: number) => void)—
value?number—Selected category index (controlled).

Design

Figma source: faq — etisalat Consumer App DSL V1.2

  • Category row: padding 16 (spacing/lg); 40px outline chips, 8 apart (spacing/sm), at least 72 wide (scale/72), scrolling sideways.
  • Stack: padding 0/16, gap 4 (spacing/xs). Each card is surface/base/inverse at border-radius/7 (20px).
  • Question row: padding 16 (spacing/lg), gap 12 (spacing/md). The question is title/xs in text/default/default, held in a 40px block (scale/40) and clamped to two lines while collapsed, which gives a 72px card. A 20px chevron sits in a 24px icon box.
  • Answer: body/md in text/default/subtle; container padding 0/4/4 (spacing/xs), text padding 0/12/16 (spacing/md, spacing/lg).

On this page