> ## 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 — Android

> Explore PrimerTheme tokens and see which components they affect, with live Kotlin 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: 'LightColorTokens'
  }, {
    name: 'primerColorBackground',
    value: '#FFFFFF',
    type: 'color',
    affects: ['checkout', 'cardForm'],
    category: 'LightColorTokens'
  }, {
    name: 'primerColorTextPrimary',
    value: '#212121',
    type: 'color',
    affects: ['cardNumber', 'expiry', 'cvv', 'cardholder', 'labels'],
    category: 'LightColorTokens'
  }, {
    name: 'primerColorTextSecondary',
    value: '#757575',
    type: 'color',
    affects: ['labels', 'placeholders'],
    category: 'LightColorTokens'
  }, {
    name: 'primerColorTextPlaceholder',
    value: '#9E9E9E',
    type: 'color',
    affects: ['cardNumber', 'expiry', 'cvv', 'cardholder'],
    category: 'LightColorTokens'
  }, {
    name: 'primerColorBorderOutlinedDefault',
    value: '#E0E0E0',
    type: 'color',
    affects: ['cardNumber', 'expiry', 'cvv', 'cardholder'],
    category: 'LightColorTokens'
  }, {
    name: 'primerColorBorderOutlinedHover',
    value: '#BDBDBD',
    type: 'color',
    affects: ['cardNumber', 'expiry', 'cvv', 'cardholder'],
    category: 'LightColorTokens'
  }, {
    name: 'primerColorBorderOutlinedError',
    value: '#FF7279',
    type: 'color',
    affects: ['errorFields', 'errorScreen'],
    category: 'LightColorTokens'
  }, {
    name: 'primerColorFocus',
    value: '#2F98FF',
    type: 'color',
    affects: ['cardNumber', 'expiry', 'cvv', 'cardholder', 'focus'],
    category: 'LightColorTokens'
  }],
  spacing: [{
    name: 'xxsmall',
    value: 2,
    unit: 'dp',
    min: 0,
    max: 8,
    affects: ['labels', 'cardForm'],
    category: 'SpacingTokens'
  }, {
    name: 'xsmall',
    value: 4,
    unit: 'dp',
    min: 0,
    max: 12,
    affects: ['labels', 'cardForm'],
    category: 'SpacingTokens'
  }, {
    name: 'small',
    value: 8,
    unit: 'dp',
    min: 0,
    max: 24,
    affects: ['cardForm', 'labels', 'errorScreen'],
    category: 'SpacingTokens'
  }, {
    name: 'medium',
    value: 12,
    unit: 'dp',
    min: 4,
    max: 32,
    affects: ['cardForm', 'cardNumber', 'expiry', 'cvv', 'submitButton'],
    category: 'SpacingTokens'
  }, {
    name: 'large',
    value: 16,
    unit: 'dp',
    min: 8,
    max: 48,
    affects: ['checkout', 'cardForm'],
    category: 'SpacingTokens'
  }, {
    name: 'xlarge',
    value: 20,
    unit: 'dp',
    min: 12,
    max: 56,
    affects: ['checkout'],
    category: 'SpacingTokens'
  }],
  radius: [{
    name: 'xsmall',
    value: 2,
    unit: 'dp',
    min: 0,
    max: 8,
    affects: ['labels'],
    category: 'RadiusTokens'
  }, {
    name: 'small',
    value: 4,
    unit: 'dp',
    min: 0,
    max: 16,
    affects: ['cardNumber', 'expiry', 'cvv', 'cardholder'],
    category: 'RadiusTokens'
  }, {
    name: 'medium',
    value: 8,
    unit: 'dp',
    min: 0,
    max: 24,
    affects: ['cardForm', 'submitButton', 'checkout'],
    category: 'RadiusTokens'
  }, {
    name: 'large',
    value: 12,
    unit: 'dp',
    min: 0,
    max: 32,
    affects: ['checkout', 'cardForm'],
    category: 'RadiusTokens'
  }],
  borderWidth: [{
    name: 'thin',
    value: 1,
    unit: 'dp',
    min: 1,
    max: 4,
    affects: ['cardNumber', 'expiry', 'cvv', 'cardholder'],
    category: 'BorderWidthTokens'
  }, {
    name: 'medium',
    value: 2,
    unit: 'dp',
    min: 1,
    max: 4,
    affects: ['focus'],
    category: 'BorderWidthTokens'
  }, {
    name: 'thick',
    value: 3,
    unit: 'dp',
    min: 1,
    max: 6,
    affects: ['errorFields'],
    category: 'BorderWidthTokens'
  }],
  typography: [{
    name: 'bodySmall',
    value: 12,
    unit: 'sp',
    min: 10,
    max: 18,
    affects: ['labels', 'errorScreen'],
    category: 'Typography'
  }, {
    name: 'bodyMedium',
    value: 14,
    unit: 'sp',
    min: 12,
    max: 20,
    affects: ['cardNumber', 'expiry', 'cvv'],
    category: 'Typography'
  }, {
    name: 'bodyLarge',
    value: 16,
    unit: 'sp',
    min: 14,
    max: 24,
    affects: ['submitButton', 'cardNumber', 'expiry', 'cvv', 'cardholder'],
    category: 'Typography'
  }, {
    name: 'titleLarge',
    value: 16,
    unit: 'sp',
    min: 14,
    max: 24,
    affects: ['submitButton'],
    category: 'Typography'
  }, {
    name: 'titleXlarge',
    value: 24,
    unit: 'sp',
    min: 18,
    max: 32,
    affects: ['checkout'],
    category: 'Typography'
  }]
};

export const COMPONENT_MAP = {
  checkout: {
    name: 'PrimerCheckout',
    color: DS.color.brand
  },
  cardForm: {
    name: 'PrimerCardForm',
    color: DS.color.success
  },
  cardNumber: {
    name: 'CardNumberField',
    color: DS.color.warning
  },
  expiry: {
    name: 'ExpiryField',
    color: DS.color.warning
  },
  cvv: {
    name: 'CvvField',
    color: DS.color.warning
  },
  cardholder: {
    name: 'CardholderField',
    color: DS.color.warning
  },
  labels: {
    name: 'Labels',
    color: DS.color.textMuted
  },
  placeholders: {
    name: 'Placeholders',
    color: DS.color.textMuted
  },
  submitButton: {
    name: 'SubmitButton',
    color: DS.color.brand
  },
  focus: {
    name: 'Focus State',
    color: DS.color.brand
  },
  errorScreen: {
    name: 'Error Screen',
    color: DS.color.error
  },
  errorFields: {
    name: 'Error Fields',
    color: DS.color.error
  }
};

export const hexToArgb = hex => {
  const h = hex.replace('#', '');
  return '0xFF' + h.toUpperCase();
};

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 spacingLines = [];
    const radiusLines = [];
    const borderLines = [];
    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('#')) {
        colorLines.push('        override val ' + name + ' = Color(' + hexToArgb(val) + ')');
      } else if (token.category === 'SpacingTokens') {
        spacingLines.push('    ' + name + ' = ' + val + '.dp');
      } else if (token.category === 'RadiusTokens') {
        radiusLines.push('    ' + name + ' = ' + val + '.dp');
      } else if (token.category === 'BorderWidthTokens') {
        borderLines.push('    ' + name + ' = ' + val + '.dp');
      }
    });
    const sections = [];
    if (colorLines.length > 0) sections.push('    lightColorTokens = object : LightColorTokens() {\n' + colorLines.join('\n') + '\n    }');
    if (spacingLines.length > 0) sections.push('    spacingTokens = SpacingTokens(\n' + spacingLines.join(',\n') + '\n    )');
    if (radiusLines.length > 0) sections.push('    radiusTokens = RadiusTokens(\n' + radiusLines.join(',\n') + '\n    )');
    if (borderLines.length > 0) sections.push('    borderWidthTokens = BorderWidthTokens(\n' + borderLines.join(',\n') + '\n    )');
    return 'val customTheme = PrimerTheme(\n' + sections.join(',\n') + '\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
  }}>
                    Class: <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="Android Preview">
            <div style={{
    padding: DS.space.xl,
    background: '#FAFAFA',
    borderRadius: '0 0 ' + DS.radius.lg + 'px ' + DS.radius.lg + 'px'
  }}>
              <Hl id="checkout">
                <Hl id="cardForm">
                  <div style={{
    background: getVal('primerColorBackground', '#FFFFFF'),
    borderRadius: 12,
    padding: 16,
    boxShadow: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.06)',
    fontFamily: 'Roboto, sans-serif'
  }}>
                    <Hl id="labels">
                      <label style={{
    display: 'block',
    fontSize: 12,
    fontWeight: 400,
    marginBottom: 4,
    color: getVal('primerColorTextSecondary', '#757575')
  }}>Card number</label>
                    </Hl>
                    <Hl id="cardNumber">
                      <div style={{
    border: '1px solid ' + getVal('primerColorBorderOutlinedDefault', '#E0E0E0'),
    borderRadius: 8,
    padding: '12px 16px',
    fontSize: 16,
    color: getVal('primerColorTextPrimary', '#212121'),
    height: 56,
    display: 'flex',
    alignItems: 'center',
    marginBottom: 8,
    background: getVal('primerColorBackground', '#FFFFFF')
  }}>4242 4242 4242 4242</div>
                    </Hl>

                    <div style={{
    display: 'grid',
    gridTemplateColumns: '1fr 1fr',
    gap: 8,
    marginBottom: 8
  }}>
                      <div>
                        <Hl id="labels">
                          <label style={{
    display: 'block',
    fontSize: 12,
    fontWeight: 400,
    marginBottom: 4,
    color: getVal('primerColorTextSecondary', '#757575')
  }}>Expiry</label>
                        </Hl>
                        <Hl id="expiry">
                          <div style={{
    border: '1px solid ' + getVal('primerColorBorderOutlinedDefault', '#E0E0E0'),
    borderRadius: 8,
    padding: '12px 16px',
    fontSize: 16,
    color: getVal('primerColorTextPlaceholder', '#9E9E9E'),
    height: 56,
    display: 'flex',
    alignItems: 'center'
  }}>MM/YY</div>
                        </Hl>
                      </div>
                      <div>
                        <Hl id="labels">
                          <label style={{
    display: 'block',
    fontSize: 12,
    fontWeight: 400,
    marginBottom: 4,
    color: getVal('primerColorTextSecondary', '#757575')
  }}>CVV</label>
                        </Hl>
                        <Hl id="cvv">
                          <div style={{
    border: '1px solid ' + getVal('primerColorBorderOutlinedDefault', '#E0E0E0'),
    borderRadius: 8,
    padding: '12px 16px',
    fontSize: 16,
    color: getVal('primerColorTextPlaceholder', '#9E9E9E'),
    height: 56,
    display: 'flex',
    alignItems: 'center'
  }}>123</div>
                        </Hl>
                      </div>
                    </div>

                    <Hl id="submitButton">
                      <button style={{
    width: '100%',
    padding: '16px 24px',
    background: getVal('primerColorBrand', '#2F98FF'),
    color: '#fff',
    border: 'none',
    borderRadius: 8,
    fontSize: 14,
    fontWeight: 500,
    letterSpacing: 0.5,
    height: 48,
    cursor: 'pointer',
    fontFamily: 'Roboto, sans-serif',
    textTransform: 'uppercase'
  }}>Pay $49.99</button>
                    </Hl>
                  </div>
                </Hl>
              </Hl>
            </div>
          </Panel>

          <Panel title="Generated Kotlin 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 `PrimerTheme` tokens affect different parts of the Android Checkout. Select a token to see which components it impacts, adjust values with sliders and color pickers, and copy the generated Kotlin code directly into your project.

<Note>
  This explorer showcases a subset of tokens from `PrimerTheme`. For a complete reference, see the [Android Color Tokens](/sdk/android-checkout/v3.0.0-beta/api/theming/color-tokens) 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 Kotlin code updates live — copy it directly into your project

***

## Applying Tokens in Your Code

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

```kotlin theme={"dark"}
val customTheme = PrimerTheme(
    lightColorTokens = object : LightColorTokens() {
        override val primerColorBrand = Color(0xFF6C5CE7)
    },
    spacingTokens = SpacingTokens(small = 10.dp, medium = 14.dp),
    radiusTokens = RadiusTokens(medium = 12.dp),
)
```

***

## Related

* [Card Form Layout Builder](/checkout/primer-checkout/build-your-ui/card-form-layout-builder) — Build card form layouts
* [Component Architecture Explorer](/checkout/primer-checkout/build-your-ui/component-architecture-explorer-android) — Compose component hierarchy
