Design System
Components

Section

The body container — a rounded surface with a header and a body slot sized to the card type it holds.

Figma description · sectionOpen in Figma ↗

Content block wrapper. Optional header (title, description, filter pill, trigger) plus a body slot holding cards or rows.

USE FOR Every content block on a page. All content components sit inside a section.

NOT FOR Fixed chrome. Topbar, navbar and tab-pill are placed directly on the screen.

SIZE

Content determines rows; rows determine size. Never resize the body manually.

xs 1 row (72) · sm 2 (148) · md 3 (224) · lg 4 (300) · xl 6 (452)

Count the rows the content needs, then pick the matching size. If the content does not fill the size, step down one. Never exceed xl.

single input field → xs or sm full highlight banner → xl

COLOUR

Set on the section, never on the content inside it.

inverse=false → default · raised · ai-powered

inverse=true → brand · midnight-base · midnight-raised · glass-midnight · glass-white

Use glass only over imagery. It has no contrast guarantee on flat colour.

Use gradient only to emphasise the most important section, and only on pages with more than two sections.

CONTENT

One major item per section, or several repeating components such as a set of forms.

A major component plus a supporting one is allowed — a stack of cards with a cta-bar below, for example — provided the total stays within xl.

GRID

Two systems. The general grid applies to the whole screen. The section grid applies inside the body slot only.

Section grid: up to 6 rows, each with 24 responsive columns and constant 4px spacing.

Columns stretch to fill the row at any container or screen width. Spacing is always 4px unless a placed component adds its own internal padding.

RULES

One section per content block.

Never nest sections.

Never stretch beyond xl.

Section
Cover these with your Smiles Points
Control from anywhere
Manage your plans
Props
import SwiftUI
import EandTokens

// V1.2 section: radius 24, header padded 16/16/0 with a 40pt field row — 24pt title
// (heading/lg style) and the trigger (40pt section-link chevron, or a tertiary lg button).
// Body padded 16; its min height reserves the row for the card type it holds.
struct SectionView<Content: View>: View {
  let title: String; let context: String
  var bodyHeight: CGFloat = EandSection.bodyHeightRow3   // row-1 72 · 2 148 · 3 224 · 4 300 · 6 452
  @ViewBuilder var content: Content
  var body: some View {
    VStack(alignment: .leading, spacing: 0) {
      VStack(alignment: .leading, spacing: EandSpacing.xs) {
        HStack(spacing: 0) {
          Text(title)
            .font(.system(size: EandTypography.headingLg.size, weight: EandTypography.headingLg.weight))
            .lineLimit(1)
          Spacer(minLength: EandSpacing.md)
          SectionLinkButton(action: .forward)    // 40pt, r14, solid white
        }
        .frame(minHeight: EandScale.scale40)
        Text(context).font(.system(size: EandTypography.bodyMd.size))
          .foregroundColor(EandColor.textDefaultSubtle)
      }
      .padding([.top, .horizontal], EandSpacing.lg)
      content
        .frame(maxWidth: .infinity, minHeight: bodyHeight, alignment: .topLeading)
        .padding(EandSpacing.lg)
    }
    .foregroundColor(EandColor.textDefaultDefault)
    .background(EandColor.surfaceBaseDefault)
    .clipShape(RoundedRectangle(cornerRadius: EandRadius.radius8))
  }
}

When to use

  • Use for every content block in a screen body: one section per group of cards, rows or tiles, with a title that says what the group is.
  • Don't use to open a page; that is PageHeader, below the TopBar. Don't nest sections. For a page-end block of questions, use FAQ on the page canvas rather than styling questions inside a section.

Anatomy

  • Header — title (24px bold, one or two lines through titleLine), an optional filterPill placed inline right after the title, and a trigger at the row end. context adds a body/md line under the row. showHeader={false} removes the header entirely.

  • Trigger — chevron (default) is the 40px section-link button; button is a tertiary lg Button labelled triggerLabel (default "See all"); none hides it. Both call onTrigger.

  • Body slot — children sit in a 16px-padded slot. size reserves the row height the card type needs:

    sizeSlot heightFits
    xs72pxlist rows, action bars
    sm148pxquick-action and service tiles
    md224pxdeal, new-on and general cards
    lg300pxplan and product cards
    xl452pxhighlight banners
  • carousel makes the body scroll sideways and bleed to the section edges. gradient adds the sunset glow behind the body; reserve it for the most important section on the screen.

Choosing a surface

surfaceFillInk
defaultsurface/base/default (#f0f0f5)default
raisedsurface/raised/default (#fff)default
ai-poweredLight red-to-grey gradientdefault
brandsurface/base/brandinverse
midnight-basesurface/base/midnightinverse
midnight-raisedsurface/raised/midnightinverse
glass-midnightsurface/glass/midnight/mdinverse
glass-whitesurface/glass/white/lginverse

Use the glass surfaces only over imagery. On the inverse surfaces, pass inverse to the filter pill yourself; the title, context and trigger switch automatically.

<Section
  title="Plans for you"
  surface="midnight-base"
  filterPill={<FilterPill inverse>Postpaid</FilterPill>}
  trigger="button"
  triggerLabel="Manage"
  size="lg"
  carousel
>
  <PlanCard colorScheme="inverse" />
  <PlanCard colorScheme="inverse" />
</Section>

Deprecated

hideChevron is now trigger="none", and onSeeAll is now onTrigger. The V1.1 surface names still work: inverse renders brand and midnight renders midnight-base. brand-muted is code-only and has no V1.2 counterpart.

Props

PropTypeDefaultDescription
carousel?boolean—Body scrolls horizontally and bleeds to the edges (carousels).
context?ReactNode—Subtext under the title (body.md, subtle).
filterPill?ReactNode—Optional filter pill, inline after the title (Figma filter-pill). Pass inverse on dark surfaces.
gradient?boolean—Sunset glow over the body (Figma .section-body gradient). Only on the most important section.
hideChevron?boolean—Deprecated. Use trigger="none".
onSeeAll?(() => void)—Deprecated. Use onTrigger.
onTrigger?(() => void)—
showHeader?booleantrueHide the whole header (Figma show-header).
size?'lg' | 'md' | 'sm' | 'xs' | 'xl'—Body slot size — reserves the row height for the card type it holds.
surface?'default' | 'inverse' | 'brand' | 'midnight' | 'raised' | 'ai-powered' | 'midnight-base' | 'midnight-raised' | 'glass-midnight' | 'glass-white' | 'brand-muted'default
title?ReactNodeSection
titleLine?1 | 21Clamp the title to one or two lines (Figma lines).
trigger?'none' | 'button' | 'chevron'—Header trigger: the section-link chevron, a tertiary lg button, or none (Figma .section-trigger link | button).
triggerLabel?ReactNodeSee allLabel for the button trigger.

Design

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

  • 24px radius (border-radius/8). Header padding 16/16/0 (spacing/lg); a 40px field row (scale/40); the filter pill sits 8px after the title (spacing/sm); the trigger has 12px of inline-start padding (spacing/md).
  • The title applies the heading/lg text style: 24px (font/size/7), 110%, snug tracking.
  • Body padding 16. Slot heights bind section/body/height/row-N: 72, 148, 224, 300 and 452.
  • The sunset glow is two layer-blurred ellipses, orange #fa700c and brand red #e00800 at 27% (25% on xs and sm), with the layer at 96%. Figma leaves these values unbound, so the code uses them as literals.
  • The ai-powered gradient (#ffe4e3 to #f5f5f7) is also unbound. The glass surfaces carry no blur, because the published surface symbols have none.

On this page