> ## Documentation Index
> Fetch the complete documentation index at: https://primer.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Design Tokens Explorer — iOS

> Explore PrimerCheckoutTheme tokens and see which components they affect, with live Swift code generation.

export const CopyButton = ({getText}) => {
  const [copied, setCopied] = useState(false);
  const [hovered, setHovered] = useState(false);
  const handleCopy = () => {
    if (navigator.clipboard) {
      navigator.clipboard.writeText(getText());
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    }
  };
  return <button onClick={handleCopy} onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} style={{
    display: 'flex',
    alignItems: 'center',
    gap: DS.space.xs,
    padding: `${DS.space.xs}px ${DS.space.sm + 2}px`,
    border: `1px solid ${copied ? DS.color.success : hovered ? DS.color.borderHover : DS.color.border}`,
    borderRadius: DS.radius.sm,
    background: copied ? DS.color.successLight : hovered ? DS.color.bgSubtle : DS.color.bgSurface,
    fontSize: DS.font.xxs,
    fontWeight: 600,
    color: copied ? DS.color.success : DS.color.textSecondary,
    cursor: 'pointer',
    transition: `all ${DS.transition.fast}`,
    outline: 'none'
  }}>{copied ? '✓ Copied' : '⎘ Copy'}</button>;
};

export const ColorPicker = ({value, onChange, label}) => <div>
    {label && <div style={{
  fontSize: DS.font.xs,
  color: DS.color.textSecondary,
  marginBottom: DS.space.xs
}}>{label}</div>}
    <div style={{
  display: 'flex',
  gap: DS.space.sm,
  alignItems: 'center'
}}>
      <div style={{
  position: 'relative'
}}>
        <div style={{
  width: 32,
  height: 32,
  borderRadius: DS.radius.sm,
  background: value,
  border: `1px solid ${DS.color.border}`,
  cursor: 'pointer'
}} />
        <input type="color" value={value.startsWith('#') ? value : '#2f98ff'} onChange={e => onChange(e.target.value)} aria-label={label} style={{
  position: 'absolute',
  inset: 0,
  opacity: 0,
  cursor: 'pointer',
  width: '100%',
  height: '100%'
}} />
      </div>
      <input type="text" value={value} onChange={e => onChange(e.target.value)} style={{
  flex: 1,
  padding: `${DS.space.xs}px ${DS.space.sm}px`,
  border: `1px solid ${DS.color.border}`,
  borderRadius: DS.radius.sm,
  fontFamily: DS.font.mono,
  fontSize: DS.font.xs,
  color: DS.color.text,
  background: DS.color.bgSurface,
  outline: 'none',
  transition: `border-color ${DS.transition.fast}`
}} onFocus={e => e.target.style.borderColor = DS.color.brand} onBlur={e => e.target.style.borderColor = DS.color.border} />
    </div>
  </div>;

export const Slider = ({min, max, value, onChange, label, unit = 'px', step}) => {
  const pct = (value - min) / (max - min) * 100;
  return <div>
      {label && <div style={{
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: DS.space.xs
  }}>
          <span style={{
    fontSize: DS.font.xs,
    color: DS.color.textSecondary
  }}>{label}</span>
          <span style={{
    fontSize: DS.font.xs,
    fontFamily: DS.font.mono,
    fontWeight: 600,
    color: DS.color.text,
    background: DS.color.bgSubtle,
    padding: `1px ${DS.space.sm}px`,
    borderRadius: DS.radius.sm,
    border: `1px solid ${DS.color.border}`
  }}>{value}{unit}</span>
        </div>}
      <div style={{
    position: 'relative',
    height: 20,
    display: 'flex',
    alignItems: 'center'
  }}>
        <div style={{
    position: 'absolute',
    left: 0,
    right: 0,
    height: 4,
    borderRadius: 2,
    background: DS.color.border
  }} />
        <div style={{
    position: 'absolute',
    left: 0,
    width: `${pct}%`,
    height: 4,
    borderRadius: 2,
    background: DS.color.brand,
    transition: `width ${DS.transition.fast}`
  }} />
        <input type="range" min={min} max={max} step={step || 1} value={value} onChange={e => onChange(parseInt(e.target.value))} aria-label={label} style={{
    position: 'absolute',
    width: '100%',
    height: 20,
    opacity: 0,
    cursor: 'pointer',
    margin: 0
  }} />
        <div style={{
    position: 'absolute',
    left: `calc(${pct}% - 8px)`,
    width: 16,
    height: 16,
    borderRadius: '50%',
    background: DS.color.bgSurface,
    border: `2px solid ${DS.color.brand}`,
    boxShadow: '0 1px 4px rgba(0,0,0,0.12)',
    pointerEvents: 'none',
    transition: `left ${DS.transition.fast}`
  }} />
      </div>
      <div style={{
    display: 'flex',
    justifyContent: 'space-between',
    marginTop: 2,
    fontSize: DS.font.xxs,
    color: DS.color.textMuted
  }}>
        <span>{min}{unit}</span>
        <span>{max}{unit}</span>
      </div>
    </div>;
};

export const Badge = ({children, color = DS.color.brand}) => <span style={{
  fontSize: DS.font.xxs - 1,
  fontWeight: 700,
  padding: `1px ${DS.space.sm - 2}px`,
  borderRadius: DS.radius.sm,
  background: `${color}15`,
  color: color,
  textTransform: 'uppercase',
  letterSpacing: '0.3px',
  whiteSpace: 'nowrap'
}}>{children}</span>;

export const Panel = ({title, children, style}) => <div style={Object.assign({
  background: DS.color.bgSurface,
  border: `1px solid ${DS.color.border}`,
  borderRadius: DS.radius.lg,
  overflow: 'hidden'
}, style)}>
    {title && <div style={{
  padding: `${DS.space.sm}px ${DS.space.lg}px`,
  borderBottom: `1px solid ${DS.color.border}`,
  fontSize: DS.font.xxs,
  fontWeight: 700,
  textTransform: 'uppercase',
  letterSpacing: '1px',
  color: DS.color.textMuted,
  background: DS.color.bgSubtle
}}>
        {title}
      </div>}
    {children}
  </div>;

export const DS = {
  color: {
    brand: 'var(--ds-color-brand, #2f98ff)',
    brandLight: 'var(--ds-color-brand-light, #e8f2ff)',
    brandMuted: 'var(--ds-color-brand-muted, rgba(47, 152, 255, 0.12))',
    focus: 'var(--ds-color-brand, #2f98ff)',
    success: 'var(--ds-color-success, #1a8a5c)',
    successLight: 'var(--ds-color-success-light, #edf8f2)',
    successMuted: 'var(--ds-color-success-muted, rgba(26, 138, 92, 0.12))',
    warning: 'var(--ds-color-warning, #c47a20)',
    warningLight: 'var(--ds-color-warning-light, #fef6eb)',
    warningMuted: 'var(--ds-color-warning-muted, rgba(196, 122, 32, 0.12))',
    error: 'var(--ds-color-error, #c44040)',
    errorLight: 'var(--ds-color-error-light, #fef0f0)',
    errorMuted: 'rgba(196, 64, 64, 0.12)',
    purple: '#7c5cbf',
    purpleLight: '#f3effc',
    purpleMuted: 'rgba(124, 92, 191, 0.12)',
    text: 'var(--ds-color-text, #1c1b18)',
    textSecondary: 'var(--ds-color-text-secondary, #5c5953)',
    textMuted: 'var(--ds-color-text-muted, #9d9a92)',
    textDisabled: '#c5c2ba',
    border: 'var(--ds-color-border, #e4e2dd)',
    borderHover: 'var(--ds-color-border-hover, #d0cec8)',
    bgPage: 'var(--ds-color-bg-page, #f5f4f1)',
    bgSurface: 'var(--ds-color-bg-surface, #ffffff)',
    bgSubtle: 'var(--ds-color-bg-subtle, #fafaf8)',
    bgOverlay: 'rgba(0, 0, 0, 0.04)'
  },
  space: {
    xxs: 2,
    xs: 4,
    sm: 8,
    md: 12,
    lg: 16,
    xl: 20,
    xxl: 24,
    xxxl: 32
  },
  font: {
    xxs: 10,
    xs: 11,
    sm: 12,
    md: 13,
    base: 14,
    lg: 15,
    xl: 16,
    mono: "ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Monaco, Consolas, monospace",
    sans: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"
  },
  radius: {
    sm: 4,
    md: 8,
    lg: 12
  },
  transition: {
    fast: '120ms ease',
    normal: '200ms ease'
  }
};

export const TOKEN_DATA = {
  colors: [{
    name: 'primerColorBrand',
    value: '#2F98FF',
    type: 'color',
    affects: ['checkout', 'submitButton', 'focus'],
    category: 'ColorOverrides'
  }, {
    name: 'primerColorBackground',
    value: '#FFFFFF',
    type: 'color',
    affects: ['checkout', 'cardForm'],
    category: 'ColorOverrides'
  }, {
    name: 'primerColorTextPrimary',
    value: '#1C1B18',
    type: 'color',
    affects: ['cardNumber', 'expiry', 'cvv', 'cardholder', 'labels'],
    category: 'ColorOverrides'
  }, {
    name: 'primerColorTextSecondary',
    value: '#5C5953',
    type: 'color',
    affects: ['labels', 'placeholders'],
    category: 'ColorOverrides'
  }, {
    name: 'primerColorTextPlaceholder',
    value: '#9D9A92',
    type: 'color',
    affects: ['cardNumber', 'expiry', 'cvv', 'cardholder'],
    category: 'ColorOverrides'
  }, {
    name: 'primerColorBorderOutlinedDefault',
    value: '#E4E2DD',
    type: 'color',
    affects: ['cardNumber', 'expiry', 'cvv', 'cardholder'],
    category: 'ColorOverrides'
  }, {
    name: 'primerColorBorderOutlinedFocus',
    value: '#2F98FF',
    type: 'color',
    affects: ['cardNumber', 'expiry', 'cvv', 'cardholder'],
    category: 'ColorOverrides'
  }, {
    name: 'primerColorBorderOutlinedError',
    value: '#C44040',
    type: 'color',
    affects: ['errorFields', 'errorScreen'],
    category: 'ColorOverrides'
  }, {
    name: 'primerColorFocus',
    value: '#2F98FF',
    type: 'color',
    affects: ['cardNumber', 'expiry', 'cvv', 'cardholder', 'focus'],
    category: 'ColorOverrides'
  }, {
    name: 'primerColorRed500',
    value: '#C44040',
    type: 'color',
    affects: ['errorScreen', 'errorFields'],
    category: 'ColorOverrides'
  }],
  radius: [{
    name: 'primerRadiusSmall',
    value: 4,
    unit: 'pt',
    min: 0,
    max: 16,
    affects: ['cardNumber', 'expiry', 'cvv', 'cardholder'],
    category: 'RadiusOverrides'
  }, {
    name: 'primerRadiusMedium',
    value: 8,
    unit: 'pt',
    min: 0,
    max: 24,
    affects: ['cardForm', 'submitButton', 'checkout'],
    category: 'RadiusOverrides'
  }, {
    name: 'primerRadiusLarge',
    value: 12,
    unit: 'pt',
    min: 0,
    max: 32,
    affects: ['checkout', 'cardForm'],
    category: 'RadiusOverrides'
  }],
  spacing: [{
    name: 'primerSpaceSmall',
    value: 8,
    unit: 'pt',
    min: 0,
    max: 24,
    affects: ['cardForm', 'labels', 'errorScreen'],
    category: 'SpacingOverrides'
  }, {
    name: 'primerSpaceMedium',
    value: 12,
    unit: 'pt',
    min: 4,
    max: 32,
    affects: ['cardForm', 'cardNumber', 'expiry', 'cvv', 'submitButton'],
    category: 'SpacingOverrides'
  }, {
    name: 'primerSpaceLarge',
    value: 16,
    unit: 'pt',
    min: 8,
    max: 48,
    affects: ['checkout', 'cardForm'],
    category: 'SpacingOverrides'
  }, {
    name: 'primerSpaceXlarge',
    value: 20,
    unit: 'pt',
    min: 12,
    max: 56,
    affects: ['checkout'],
    category: 'SpacingOverrides'
  }],
  sizes: [{
    name: 'primerSizeMedium',
    value: 40,
    unit: 'pt',
    min: 32,
    max: 56,
    affects: ['cardNumber', 'expiry', 'cvv', 'cardholder'],
    category: 'SizeOverrides'
  }, {
    name: 'primerSizeLarge',
    value: 48,
    unit: 'pt',
    min: 40,
    max: 64,
    affects: ['submitButton'],
    category: 'SizeOverrides'
  }]
};

export const COMPONENT_MAP = {
  checkout: {
    name: 'PrimerCheckout',
    color: DS.color.brand
  },
  cardForm: {
    name: 'Card Form',
    color: DS.color.success
  },
  cardNumber: {
    name: 'Card Number',
    color: DS.color.warning
  },
  expiry: {
    name: 'Expiry',
    color: DS.color.warning
  },
  cvv: {
    name: 'CVV',
    color: DS.color.warning
  },
  cardholder: {
    name: 'Cardholder',
    color: DS.color.warning
  },
  labels: {
    name: 'Labels',
    color: DS.color.textMuted
  },
  placeholders: {
    name: 'Placeholders',
    color: DS.color.textMuted
  },
  submitButton: {
    name: 'Submit Button',
    color: DS.color.brand
  },
  focus: {
    name: 'Focus Ring',
    color: DS.color.brand
  },
  errorScreen: {
    name: 'Error Screen',
    color: DS.color.error
  },
  errorFields: {
    name: 'Error Fields',
    color: DS.color.error
  }
};

export const hexToRgb = hex => {
  const h = hex.replace('#', '');
  const r = parseInt(h.substring(0, 2), 16);
  const g = parseInt(h.substring(2, 4), 16);
  const b = parseInt(h.substring(4, 6), 16);
  return {
    r: r,
    g: g,
    b: b
  };
};

export const TokenRow = ({token, isSelected, value, onClick}) => {
  const [hovered, setHovered] = useState(false);
  return <button onClick={onClick} onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} style={{
    display: 'block',
    width: '100%',
    padding: DS.space.md,
    border: 'none',
    borderBottom: '1px solid ' + DS.color.bgSubtle,
    background: isSelected ? DS.color.brandLight : hovered ? DS.color.bgSubtle : DS.color.bgSurface,
    cursor: 'pointer',
    textAlign: 'left',
    transition: 'background ' + DS.transition.fast
  }}>
      <div style={{
    fontFamily: DS.font.mono,
    fontSize: DS.font.xs,
    color: DS.color.text,
    fontWeight: 500
  }}>
        {token.name}
      </div>
      <div style={{
    fontSize: DS.font.xs,
    color: DS.color.textMuted,
    marginTop: 2,
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'center'
  }}>
        {token.type === 'color' ? <span style={{
    display: 'inline-flex',
    alignItems: 'center',
    gap: DS.space.xs
  }}>
            <span style={{
    width: 12,
    height: 12,
    borderRadius: 2,
    background: typeof value === 'string' ? value : token.value,
    border: '1px solid ' + DS.color.border
  }} />
            {value}
          </span> : <span>{value}{token.unit}</span>}
        <Badge color={DS.color.textMuted}>{token.affects.length}</Badge>
      </div>
    </button>;
};

export const DesignTokensExplorer = () => {
  const [mode, setMode] = useState('token');
  const [selectedToken, setSelectedToken] = useState(null);
  const [selectedComponent, setSelectedComponent] = useState(null);
  const [category, setCategory] = useState('colors');
  const [customValues, setCustomValues] = useState({});
  const highlighted = useMemo(() => {
    if (mode === 'token' && selectedToken) return selectedToken.affects;
    if (mode === 'component' && selectedComponent) return [selectedComponent];
    return [];
  }, [mode, selectedToken, selectedComponent]);
  const getVal = (name, fallback) => {
    if (customValues[name] !== undefined) return customValues[name];
    return fallback;
  };
  const isHl = id => highlighted.includes(id);
  const Hl = ({id, children}) => <div style={{
    position: 'relative',
    borderRadius: 'inherit'
  }}>
      {children}
      {isHl(id) && <div style={{
    position: 'absolute',
    inset: -2,
    border: '2px solid ' + DS.color.success,
    borderRadius: 'inherit',
    background: DS.color.success + '12',
    pointerEvents: 'none',
    transition: 'all ' + DS.transition.normal
  }} />}
    </div>;
  const generateCode = useMemo(() => {
    const changed = Object.keys(customValues);
    if (changed.length === 0) return '// Adjust tokens on the left to generate code';
    const colorLines = [];
    const radiusLines = [];
    const spacingLines = [];
    const sizeLines = [];
    changed.forEach(name => {
      const allTokens = Object.values(TOKEN_DATA).flat();
      const token = allTokens.find(t => t.name === name);
      if (!token) return;
      const val = customValues[name];
      if (token.type === 'color' && typeof val === 'string' && val.startsWith('#')) {
        const rgb = hexToRgb(val);
        colorLines.push('    ' + name + ': Color(red: ' + (rgb.r / 255).toFixed(2) + ', green: ' + (rgb.g / 255).toFixed(2) + ', blue: ' + (rgb.b / 255).toFixed(2) + ')');
      } else if (token.category === 'RadiusOverrides') {
        radiusLines.push('    ' + name + ': ' + val);
      } else if (token.category === 'SpacingOverrides') {
        spacingLines.push('    ' + name + ': ' + val);
      } else if (token.category === 'SizeOverrides') {
        sizeLines.push('    ' + name + ': ' + val);
      }
    });
    const sections = [];
    if (colorLines.length > 0) sections.push('  colors: ColorOverrides(\n' + colorLines.join(',\n') + '\n  )');
    if (radiusLines.length > 0) sections.push('  radius: RadiusOverrides(\n' + radiusLines.join(',\n') + '\n  )');
    if (spacingLines.length > 0) sections.push('  spacing: SpacingOverrides(\n' + spacingLines.join(',\n') + '\n  )');
    if (sizeLines.length > 0) sections.push('  sizes: SizeOverrides(\n' + sizeLines.join(',\n') + '\n  )');
    return 'let theme = PrimerCheckoutTheme(\n' + sections.join(',\n') + '\n)\n\nPrimerCheckout(\n  clientToken: clientToken,\n  primerTheme: theme\n)';
  }, [customValues]);
  return <div>
      <div style={{
    display: 'flex',
    gap: DS.space.sm,
    alignItems: 'center',
    marginBottom: DS.space.lg,
    flexWrap: 'wrap'
  }}>
        <div style={{
    display: 'inline-flex',
    background: DS.color.bgSubtle,
    borderRadius: DS.radius.md,
    padding: 3,
    border: '1px solid ' + DS.color.border
  }}>
          {[{
    id: 'token',
    label: 'Token → Components'
  }, {
    id: 'component',
    label: 'Component → Tokens'
  }].map(m => <button key={m.id} onClick={() => {
    setMode(m.id);
    setSelectedToken(null);
    setSelectedComponent(null);
  }} style={{
    padding: DS.space.sm + 'px ' + (DS.space.md + 2) + 'px',
    border: 'none',
    borderRadius: DS.radius.sm + 2,
    background: mode === m.id ? DS.color.bgSurface : 'transparent',
    color: mode === m.id ? DS.color.text : DS.color.textMuted,
    fontWeight: 500,
    fontSize: DS.font.sm,
    cursor: 'pointer',
    boxShadow: mode === m.id ? '0 1px 3px rgba(0,0,0,0.08)' : 'none',
    transition: 'all ' + DS.transition.fast
  }}>{m.label}</button>)}
        </div>
        {Object.keys(customValues).length > 0 && <button onClick={() => setCustomValues({})} style={{
    padding: DS.space.sm + 'px ' + DS.space.md + 'px',
    border: '1px solid ' + DS.color.error + '40',
    borderRadius: DS.radius.sm,
    background: DS.color.bgSurface,
    color: DS.color.error,
    fontSize: DS.font.sm,
    cursor: 'pointer'
  }}>Reset All</button>}
      </div>

      <div style={{
    display: 'grid',
    gridTemplateColumns: '260px 1fr',
    gap: DS.space.xl
  }}>
        <div>
          {mode === 'token' ? <div>
              <div style={{
    display: 'flex',
    flexWrap: 'wrap',
    gap: DS.space.xs,
    marginBottom: DS.space.md
  }}>
                {Object.keys(TOKEN_DATA).map(cat => <button key={cat} onClick={() => {
    setCategory(cat);
    setSelectedToken(null);
  }} style={{
    padding: DS.space.xs + 1 + 'px ' + (DS.space.sm + 2) + 'px',
    border: 'none',
    borderRadius: DS.radius.sm + 2,
    background: category === cat ? DS.color.brand : DS.color.bgSubtle,
    color: category === cat ? '#fff' : DS.color.textSecondary,
    fontSize: DS.font.xs,
    fontWeight: 500,
    cursor: 'pointer',
    textTransform: 'capitalize',
    transition: 'all ' + DS.transition.fast
  }}>{cat}</button>)}
              </div>
              <Panel>
                <div>
                  {TOKEN_DATA[category].map((token, i) => {
    const isSel = selectedToken?.name === token.name;
    return <TokenRow key={token.name} token={token} isSelected={isSel} value={customValues[token.name] !== undefined ? customValues[token.name] : token.value} onClick={() => setSelectedToken(isSel ? null : token)} />;
  })}
                </div>
              </Panel>
              {selectedToken && <div style={{
    marginTop: DS.space.lg,
    padding: DS.space.md,
    background: DS.color.bgSubtle,
    borderRadius: DS.radius.md,
    border: '1px solid ' + DS.color.border
  }}>
                  <div style={{
    fontSize: DS.font.xxs,
    fontWeight: 700,
    color: DS.color.textMuted,
    textTransform: 'uppercase',
    letterSpacing: '0.5px',
    marginBottom: DS.space.sm + 2
  }}>
                    Adjust Value
                  </div>
                  {selectedToken.type === 'color' ? <ColorPicker value={customValues[selectedToken.name] || selectedToken.value} onChange={val => setCustomValues(prev => Object.assign({}, prev, {
    [selectedToken.name]: val
  }))} /> : <Slider min={selectedToken.min} max={selectedToken.max} value={customValues[selectedToken.name] !== undefined ? customValues[selectedToken.name] : selectedToken.value} onChange={val => setCustomValues(prev => Object.assign({}, prev, {
    [selectedToken.name]: val
  }))} unit={selectedToken.unit} />}
                  <div style={{
    marginTop: DS.space.sm + 2,
    fontSize: DS.font.xxs,
    color: DS.color.textMuted
  }}>
                    Affects: {selectedToken.affects.map((a, i) => <span key={a}>
                        <span style={{
    fontFamily: DS.font.mono,
    color: DS.color.success
  }}>{COMPONENT_MAP[a] ? COMPONENT_MAP[a].name : a}</span>
                        {i < selectedToken.affects.length - 1 && ', '}
                      </span>)}
                  </div>
                  <div style={{
    marginTop: DS.space.sm,
    fontSize: DS.font.xxs,
    color: DS.color.textMuted
  }}>
                    Struct: <span style={{
    fontFamily: DS.font.mono
  }}>{selectedToken.category}</span>
                  </div>
                </div>}
            </div> : <Panel title="Select a component">
              <div style={{
    padding: DS.space.md
  }}>
                {selectedComponent && COMPONENT_MAP[selectedComponent] ? <div>
                    <div style={{
    fontFamily: DS.font.mono,
    fontSize: DS.font.base,
    fontWeight: 600,
    color: COMPONENT_MAP[selectedComponent].color,
    marginBottom: DS.space.md
  }}>
                      {COMPONENT_MAP[selectedComponent].name}
                    </div>
                    {Object.values(TOKEN_DATA).flat().filter(t => t.affects.includes(selectedComponent)).map(token => <div key={token.name} style={{
    padding: DS.space.sm + 2,
    background: DS.color.bgSubtle,
    borderRadius: DS.radius.sm,
    marginBottom: DS.space.sm
  }}>
                          <div style={{
    fontFamily: DS.font.mono,
    fontSize: DS.font.xs,
    fontWeight: 500,
    marginBottom: DS.space.sm,
    color: DS.color.text
  }}>
                            {token.name}
                          </div>
                          {token.type === 'color' ? <ColorPicker value={customValues[token.name] || token.value} onChange={val => setCustomValues(prev => Object.assign({}, prev, {
    [token.name]: val
  }))} /> : <Slider min={token.min} max={token.max} value={customValues[token.name] !== undefined ? customValues[token.name] : token.value} onChange={val => setCustomValues(prev => Object.assign({}, prev, {
    [token.name]: val
  }))} unit={token.unit} />}
                        </div>)}
                  </div> : <div>
                    {Object.entries(COMPONENT_MAP).map(entry => {
    const id = entry[0];
    const comp = entry[1];
    return <button key={id} onClick={() => setSelectedComponent(id)} style={{
      display: 'flex',
      alignItems: 'center',
      gap: DS.space.sm,
      width: '100%',
      padding: DS.space.sm + 'px ' + DS.space.md + 'px',
      border: 'none',
      borderBottom: '1px solid ' + DS.color.bgSubtle,
      background: DS.color.bgSurface,
      cursor: 'pointer',
      textAlign: 'left'
    }}>
                          <span style={{
      width: 8,
      height: 8,
      borderRadius: 2,
      background: comp.color,
      flexShrink: 0
    }} />
                          <span style={{
      fontFamily: DS.font.mono,
      fontSize: DS.font.xs,
      color: DS.color.text
    }}>{comp.name}</span>
                        </button>;
  })}
                  </div>}
              </div>
            </Panel>}
        </div>

        <div style={{
    display: 'flex',
    flexDirection: 'column',
    gap: DS.space.lg
  }}>
          <Panel title="iOS Preview">
            <div style={{
    padding: DS.space.xl,
    background: '#F2F2F7',
    borderRadius: '0 0 ' + DS.radius.lg + 'px ' + DS.radius.lg + 'px'
  }}>
              <Hl id="checkout">
                <Hl id="cardForm">
                  <div style={{
    background: getVal('primerColorBackground', '#FFFFFF'),
    borderRadius: getVal('primerRadiusMedium', 8),
    padding: getVal('primerSpaceLarge', 16),
    boxShadow: '0 1px 4px rgba(0,0,0,0.08)',
    fontFamily: '-apple-system, system-ui, sans-serif'
  }}>
                    <Hl id="labels">
                      <label style={{
    display: 'block',
    fontSize: 13,
    fontWeight: 500,
    marginBottom: getVal('primerSpaceSmall', 8),
    color: getVal('primerColorTextSecondary', '#5C5953')
  }}>Card number</label>
                    </Hl>
                    <Hl id="cardNumber">
                      <div style={{
    border: '1px solid ' + getVal('primerColorBorderOutlinedDefault', '#E4E2DD'),
    borderRadius: getVal('primerRadiusSmall', 4),
    padding: '12px 14px',
    fontSize: 16,
    color: getVal('primerColorTextPrimary', '#1C1B18'),
    height: getVal('primerSizeMedium', 40),
    display: 'flex',
    alignItems: 'center',
    marginBottom: getVal('primerSpaceMedium', 12),
    background: getVal('primerColorBackground', '#FFFFFF')
  }}>4242 4242 4242 4242</div>
                    </Hl>

                    <div style={{
    display: 'grid',
    gridTemplateColumns: '1fr 1fr',
    gap: getVal('primerSpaceSmall', 8),
    marginBottom: getVal('primerSpaceMedium', 12)
  }}>
                      <div>
                        <Hl id="labels">
                          <label style={{
    display: 'block',
    fontSize: 13,
    fontWeight: 500,
    marginBottom: getVal('primerSpaceSmall', 8),
    color: getVal('primerColorTextSecondary', '#5C5953')
  }}>Expiry</label>
                        </Hl>
                        <Hl id="expiry">
                          <div style={{
    border: '1px solid ' + getVal('primerColorBorderOutlinedDefault', '#E4E2DD'),
    borderRadius: getVal('primerRadiusSmall', 4),
    padding: '12px 14px',
    fontSize: 16,
    color: getVal('primerColorTextPlaceholder', '#9D9A92'),
    height: getVal('primerSizeMedium', 40),
    display: 'flex',
    alignItems: 'center'
  }}>MM/YY</div>
                        </Hl>
                      </div>
                      <div>
                        <Hl id="labels">
                          <label style={{
    display: 'block',
    fontSize: 13,
    fontWeight: 500,
    marginBottom: getVal('primerSpaceSmall', 8),
    color: getVal('primerColorTextSecondary', '#5C5953')
  }}>CVV</label>
                        </Hl>
                        <Hl id="cvv">
                          <div style={{
    border: '1px solid ' + getVal('primerColorBorderOutlinedDefault', '#E4E2DD'),
    borderRadius: getVal('primerRadiusSmall', 4),
    padding: '12px 14px',
    fontSize: 16,
    color: getVal('primerColorTextPlaceholder', '#9D9A92'),
    height: getVal('primerSizeMedium', 40),
    display: 'flex',
    alignItems: 'center'
  }}>123</div>
                        </Hl>
                      </div>
                    </div>

                    <Hl id="submitButton">
                      <button style={{
    width: '100%',
    padding: '14px 20px',
    background: getVal('primerColorBrand', '#2F98FF'),
    color: '#fff',
    border: 'none',
    borderRadius: getVal('primerRadiusMedium', 8),
    fontSize: 17,
    fontWeight: 600,
    height: getVal('primerSizeLarge', 48),
    cursor: 'pointer',
    fontFamily: '-apple-system, system-ui, sans-serif'
  }}>Pay $49.99</button>
                    </Hl>
                  </div>
                </Hl>
              </Hl>
            </div>
          </Panel>

          <Panel title="Generated Swift Code">
            <div style={{
    position: 'relative'
  }}>
              <div style={{
    position: 'absolute',
    top: DS.space.sm,
    right: DS.space.sm,
    zIndex: 1
  }}>
                <CopyButton getText={() => generateCode} />
              </div>
              <pre style={{
    padding: DS.space.lg,
    paddingRight: DS.space.xxxl + DS.space.xl,
    fontFamily: DS.font.mono,
    fontSize: DS.font.xs,
    lineHeight: 1.7,
    color: DS.color.textSecondary,
    overflowX: 'auto',
    background: DS.color.bgSurface,
    margin: 0,
    whiteSpace: 'pre',
    tabSize: 2,
    minHeight: 100
  }}>{generateCode}</pre>
            </div>
          </Panel>

          <div style={{
    display: 'flex',
    gap: DS.space.lg,
    fontSize: DS.font.xs,
    color: DS.color.textMuted
  }}>
            <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: DS.space.sm
  }}>
              <div style={{
    width: 14,
    height: 14,
    background: DS.color.successLight,
    border: '2px solid ' + DS.color.success,
    borderRadius: DS.radius.sm
  }} />
              Affected
            </div>
            <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: DS.space.sm
  }}>
              <div style={{
    width: 14,
    height: 14,
    background: DS.color.warningLight,
    border: '2px solid ' + DS.color.warning,
    borderRadius: DS.radius.sm
  }} />
              Selected
            </div>
          </div>
        </div>
      </div>
    </div>;
};

Explore how `PrimerCheckoutTheme` tokens affect different parts of the iOS Checkout. Select a token to see which components it impacts, adjust values with sliders and color pickers, and copy the generated Swift code directly into your project.

<Note>
  This explorer showcases a subset of tokens from `PrimerCheckoutTheme`. For a complete reference, see the [Theming](/sdk/ios-checkout/v3.0.0-beta/api-reference/theming) page.
</Note>

<DesignTokensExplorer />

***

## How to Use

1. **Token → Components mode**: Click a token to see which parts of the checkout it affects (highlighted in green)
2. **Component → Tokens mode**: Click a component to see which tokens control its appearance
3. **Adjust values**: Use the sliders and color pickers to preview changes in real-time
4. **Copy generated code**: The Swift code updates live — copy it directly into your project

***

## Applying Tokens in Your Code

Once you've configured your theme, apply it when creating `PrimerCheckout`:

```swift theme={"dark"}
let theme = PrimerCheckoutTheme(
  colors: ColorOverrides(
    primerColorBrand: Color(red: 0.49, green: 0.36, blue: 0.75)
  ),
  radius: RadiusOverrides(
    primerRadiusMedium: 12
  )
)

PrimerCheckout(
  clientToken: clientToken,
  primerTheme: theme
)
```

***

## Related

* [Theming](/sdk/ios-checkout/v3.0.0-beta/api-reference/theming) — Theming API reference
* [Card Form Layout Builder](/checkout/primer-checkout/build-your-ui/card-form-layout-builder) — Build card form layouts
* [Scope Architecture Explorer](/checkout/primer-checkout/build-your-ui/scope-architecture-explorer-ios) — Explore scope hierarchy
