Skip to main content

Theming

v3 introduces a design token system that lets you apply consistent visual styles across all elements from a single configuration object. Tokens control colors, typography, spacing, and borders — all from a single theme object passed at initialization.

How theming works

Theme tokens are passed to BasisTheory() at initialization and injected into every element iframe as CSS variables. Elements use those variables for their appearance.

BasisTheory(key, { theme, darkTheme, themeMode })
└─ injected as CSS variables into all element iframes

Basic setup

import BasisTheory from '@basis-theory/web-elements';

const bt = BasisTheory('<PUBLIC_API_KEY>', {
themeMode: 'auto', // 'light' | 'dark' | 'auto' — defaults to 'auto'
theme: {
colors: {
primary: '#6366f1',
error: '#ef4444',
success: '#22c55e',
text: {
primary: '#111827',
secondary: '#6b7280',
placeholder: '#9ca3af',
disabled: '#d1d5db',
},
background: {
default: '#ffffff',
hover: '#f9fafb',
focus: '#f5f3ff',
disabled: '#f3f4f6',
},
border: {
default: '#d1d5db',
hover: '#a5b4fc',
focus: '#6366f1',
error: '#ef4444',
},
},
typography: {
fontFamily: '"Inter", sans-serif',
fontSize: {
sm: '14px',
base: '16px',
lg: '18px',
},
fontWeight: {
normal: '400',
medium: '500',
semibold: '600',
},
},
spacing: {
xs: '4px',
sm: '8px',
md: '12px',
lg: '16px',
xl: '24px',
},
borders: {
radius: {
none: '0',
sm: '2px',
base: '6px',
lg: '12px',
full: '9999px',
},
width: {
thin: '1px',
base: '1.5px',
thick: '2px',
},
},
},
});

Dark mode

Pass a darkTheme object to provide a separate set of tokens for dark mode. When themeMode is 'auto', the SDK applies theme or darkTheme based on the OS prefers-color-scheme media query.

const bt = BasisTheory('<PUBLIC_API_KEY>', {
themeMode: 'auto',
theme: {
colors: {
primary: '#6366f1',
text: { primary: '#111827' },
background: { default: '#ffffff' },
border: { default: '#d1d5db', focus: '#6366f1' },
error: '#ef4444',
success: '#22c55e',
},
typography: { fontFamily: 'Inter, sans-serif', fontSize: { base: '16px' }, fontWeight: { normal: '400' } },
spacing: { sm: '8px', md: '12px', lg: '16px' },
borders: { radius: { base: '6px' }, width: { base: '1.5px' } },
},
darkTheme: {
colors: {
primary: '#818cf8',
text: { primary: '#f9fafb' },
background: { default: '#1f2937' },
border: { default: '#374151', focus: '#818cf8' },
error: '#f87171',
success: '#4ade80',
},
typography: { fontFamily: 'Inter, sans-serif', fontSize: { base: '16px' }, fontWeight: { normal: '400' } },
spacing: { sm: '8px', md: '12px', lg: '16px' },
borders: { radius: { base: '6px' }, width: { base: '1.5px' } },
},
});

If darkTheme is omitted and themeMode is 'auto' or 'dark', elements fall back to using the light theme tokens.

Switching theme mode at runtime

Call bt.updateThemeMode() to switch all mounted elements to a new mode without remounting:

// Toggle between light and dark
const current = getCurrentMode(); // your own state
await bt.updateThemeMode(current === 'light' ? 'dark' : 'light');

To update a single element's theme mode without affecting others, use element.update():

await cardNumberEl.update({ themeMode: 'dark' });

Design token reference

All tokens have built-in defaults — you only need to provide the tokens you want to override.

colors

TokenTypeDescription
colors.primarystringPrimary accent color (focus rings, active states)
colors.errorstringError state color
colors.successstringSuccess / complete state color
colors.text.primarystringMain input text color
colors.text.secondarystringSecondary text color
colors.text.placeholderstringPlaceholder text color
colors.text.disabledstringText color when element is disabled
colors.background.defaultstringDefault background
colors.background.hoverstringBackground on hover
colors.background.focusstringBackground when focused
colors.background.disabledstringBackground when disabled
colors.border.defaultstringDefault border color
colors.border.hoverstringBorder color on hover
colors.border.focusstringBorder color when focused
colors.border.errorstringBorder color in error state

typography

TokenTypeDescription
typography.fontFamilystringFont stack
typography.fontSize.smstringSmall font size
typography.fontSize.basestringDefault font size
typography.fontSize.lgstringLarge font size
typography.fontWeight.normalstringNormal weight
typography.fontWeight.mediumstringMedium weight
typography.fontWeight.semiboldstringSemibold weight
typography.lineHeight.tightstringTight line height
typography.lineHeight.normalstringNormal line height
typography.lineHeight.relaxedstringRelaxed line height

spacing

TokenDescription
spacing.xsExtra-small spacing
spacing.smSmall spacing
spacing.mdMedium spacing
spacing.lgLarge spacing
spacing.xlExtra-large spacing

borders

TokenDescription
borders.radius.noneNo radius ('0')
borders.radius.smSmall radius
borders.radius.baseDefault radius
borders.radius.mdMedium radius
borders.radius.lgLarge radius
borders.radius.fullFull / pill radius
borders.width.thinThin border width
borders.width.baseDefault border width
borders.width.thickThick border width

shadows (optional)

shadows: {
none: 'none',
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
base: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1)',
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)',
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)',
focus: '0 0 0 3px rgba(59, 130, 246, 0.1)',
}

transitions (optional)

transitions: {
fast: '150ms cubic-bezier(0.4, 0, 0.2, 1)',
base: '200ms cubic-bezier(0.4, 0, 0.2, 1)',
slow: '300ms cubic-bezier(0.4, 0, 0.2, 1)',
}