Design System
Components

Button

Pill-shaped action button — primary, secondary, tertiary, link and glass, in three sizes, with a scheme for light, red and dark backgrounds.

Figma description · buttonOpen in Figma ↗

A button enables the user to initiate an action or process.

Use for: Any tappable call to action — confirming, submitting, navigating forward, or triggering a flow.

Not for: Passive navigation between views; use tab-pill or navigation bar instead. Not for destructive confirmation without a supporting pattern.

Choosing: Use primary for the single strongest action on a screen. Use secondary for supporting actions alongside a primary. Use tertiary for low-emphasis inline actions that need no fill. Use link for the lowest-weight action, typically in dense or text-heavy contexts. Use glass only over imagery or glass surfaces. Match size to the component it lives in — sm (32px) for compact slots, md (40px) for standard, lg (48px) for hero or primary CTAs. Set scheme=midnight on dark surfaces, scheme=inverse on brand-coloured surfaces, scheme=default everywhere else. Width follows the container — do not set a fixed width.

Rules: Use one primary button per screen or section. Do not place a glass button on a flat colour surface. In RTL, icon order and layout direction reverse automatically — do not manually mirror. Do not disable a button without providing context elsewhere on the screen for why the action is unavailable.

Props
import SwiftUI
import EandTokens

// V1.2 button: pill, lg 48 / md 40 / sm 32 tall, inline inset 16 / 16 / 10.
// Secondary is a 1pt INSIDE stroke (strokeBorder), so it never grows the box.
// Tertiary and link hug their text: no height, no padding.
struct EandButton: View {
  enum Variant { case primary, secondary, tertiary }
  let title: String
  var variant: Variant = .primary
  var action: () -> Void
  var body: some View {
    Button(action: action) {
      Text(title)
        .font(.system(size: EandTypography.buttonLg.size, weight: EandTypography.buttonLg.weight))
        .foregroundColor(ink)
        .padding(.horizontal, variant == .tertiary ? 0 : EandSpacing.lg)
        .frame(minWidth: variant == .tertiary ? nil : EandScale.scale48,
               minHeight: variant == .tertiary ? nil : EandScale.scale48)
        .background(variant == .primary ? EandColor.buttonPrimarySurfaceBrandDefault : .clear)
        .overlay {
          if variant == .secondary {
            Capsule().strokeBorder(EandColor.buttonSecondaryBorderBrandDefault, lineWidth: EandBorder.sm)
          }
        }
        .clipShape(Capsule())
    }
  }
  private var ink: Color {
    switch variant {
    case .primary: return EandColor.buttonPrimaryTextBrandDefault
    case .secondary: return EandColor.buttonSecondaryTextBrandDefault
    case .tertiary: return EandColor.buttonTertiaryTextBrandDefault
    }
  }
}

When to use

  • Use primary for the one main action on a screen or sheet, secondary beside it for the alternative, and tertiary or link for low-emphasis text actions. Use glass on imagery and brand surfaces.
  • Use block for full-width actions, such as those in a CTA Bar.
  • Don't use for the legal links in a consent row (ConsentCheckbox sets them), for filters (Chip, FilterPill) or for a section's “See all” (SectionLink).

Choosing a scheme

schemePrimary, tertiary, linkSecondary
defaultBrand redRed outline
inverseWhite, for red and dark backgroundsDark (midnight) outline
midnightInkWhite outline

Secondary follows the published symbol, whose bindings are crossed relative to every other type: scheme="inverse" is the dark outline and scheme="midnight" the white one. On a red or dark background, pair primary scheme="inverse" with secondary scheme="midnight". Whether the crossing is intended is an open design question. Glass has no scheme.

Sizes and states

  • Sizes — lg 48, md 40, sm 32. The icon slots are 24 / 20 / 16, so pass <Icon size> 20 / 16 / 12 to leadingIcon and trailingIcon.
  • Tertiary and link hug their text, with no height or padding. Link is set in body type and is underlined only while pressed.
  • Pressed follows the pointer, or Space and Enter while held; it never lingers on focus. Keyboard focus shows a 2px ring. Figma has no focus state, so the ring is a code-side accessibility addition.
  • Disabled uses the disabled tokens, with no opacity. Loading puts a spinner in the leading slot and suppresses clicks.

surface and tone are deprecated aliases from V1.1; scheme wins when both are set. Rendering is identical except for secondary:

DeprecatedNew (primary, tertiary, link, glass)New (secondary)
surface="brand"scheme="default"scheme="default"
surface="inverse-brand", "white", tone="inverse"scheme="inverse"scheme="midnight"
surface="midnight"scheme="midnight"scheme="inverse"

Props

PropTypeDefaultDescription
block?boolean—Stretch to fill the container width (block CTA).
leadingIcon?ReactNode—Leading icon. The slot is 24 / 20 / 16 (lg / md / sm) with 2px inset, so pass an <Icon size> of 20 / 16 / 12.
loading?boolean—Show a spinner in the leading-icon slot and suppress clicks (Figma .button-icon loader=true). Keeps the default palette — loading is not the disabled state.
scheme?'default' | 'inverse' | 'midnight'—Colour scheme (Figma V1.2 scheme). Takes precedence over surface and tone. Secondary follows the published symbol, whose bindings are crossed relative to the other types: scheme="inverse" is the dark (midnight) outline and scheme="midnight" is the white outline. Logged as a design question.
size?'lg' | 'md' | 'sm'md
surface?'brand' | 'midnight' | 'white' | 'inverse-brand'—Deprecated. Use scheme. Kept as a back-compat alias; scheme wins when both are set.
tone?'inverse' | 'brand'—Deprecated. Use scheme. Kept as a back-compat alias.
trailingIcon?ReactNode—Trailing icon — same slot sizes as leadingIcon.
variant?'link' | 'glass' | 'primary' | 'secondary' | 'tertiary'primary

For UAE PASS sign-in, use UaePassButton: it is its own published component, not a Button variant.

Design

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

  • Height and minimum width scale/48·40·32; radius border-radius/9.
  • Inline inset 16 / 16 / 10: the outer spacing/sm (spacing/xs at sm), plus the content's spacing/xs, plus the label's spacing/xs (spacing/2xs at sm).
  • Secondary has a 1px (border/sm) inside stroke; pressed is a solid fill with no stroke.
  • Labels are button/lg·md·sm; link uses body/lg·md·sm. Glass is button/glass/* behind a 20px backdrop blur.

On this page