Skip to main content
Card Element is the secure card entry component in RinneJS. It renders sensitive fields in an isolated context and returns encrypted values and validation state for your checkout UI. Use Card Element for all card transactions. Rinne expects encrypted values from mountedCard.values and does not accept plain PAN/CVC.

Create a Card Element

const cardElement = await rinne.elements.card({
  theme: 'material',
  locale: 'pt',
  fields: ['number', 'expiry', 'cvc'],
  icons: true
})

const mountedCard = await cardElement.mount('#card-element')
To process real card transactions, the merchant or organization must have an active Rinne affiliation for card processing.

Capture Payload Contract

cardElement.mount() returns a mountedCard object. In production checkout, send mountedCard.values.card to your backend.
interface MountedCard {
  isValid: boolean
  isComplete: boolean
  values: {
    card: {
      number: string // encrypted
      cvv: string // encrypted
      expiry_month: string // zero-padded, e.g. '01'
      expiry_year: string // four digits, e.g. '2026'
      name?: string
      brand?: string
      last_digits?: string
    }
  }
  unmount(): void
}
Before the user finishes input, fields in mountedCard.values.card can still be empty strings.
number and cvv are encrypted in mountedCard.values.card. expiry_month and expiry_year are plain strings.

Options

interface CardElementOptions {
  theme?: 'clean' | 'minimal' | 'material' | ThemeConfig | ThemeFunction
  colorScheme?: 'light' | 'dark' // iframe root color-scheme; match your page theme
  locale?: 'pt' | 'en' | 'es' // default: 'pt'
  icons?: boolean
  fields?: ('name' | 'number' | 'expiry' | 'cvc')[]
  onReady?: () => void
  onChange?: (state: CardState) => void
  onComplete?: (state: CardState) => void
  onFocus?: (event: CardFieldEvent) => void
  onBlur?: (event: CardFieldEvent) => void
}

Event Lifecycle

EventWhen it firesRecommended use
onReady()Card iframe is mounted and interactiveHide loading state
onChange(state)Any input/value/validation changeKeep submit button and UI state in sync
onComplete(state)All configured fields are filledTrigger pre-submit checks
onFocus(event)A field receives focusClear stale field-level errors
onBlur(event)A field loses focusShow field-level validation message
onComplete means all fields are filled. It does not guarantee the values are valid.

colorScheme

Set colorScheme to the same value as your page theme so the card iframe blends correctly with light or dark surfaces.
const colorScheme =
  document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'

const cardElement = await rinne.elements.card({
  theme: 'material',
  colorScheme,
  locale: 'pt',
  fields: ['number', 'expiry', 'cvc']
})

Field Control

// Default visible fields
await rinne.elements.card({ fields: ['number', 'expiry', 'cvc'] })

// Include cardholder name
await rinne.elements.card({ fields: ['name', 'number', 'expiry', 'cvc'] })

Validation State

interface CardState {
  isValid: boolean
  isComplete: boolean
  errors: {
    number?: string
    expiry?: string
    cvc?: string
    name?: string
  }
}
Use onChange to enable/disable your submit button.
const cardElement = await rinne.elements.card({
  onChange: (state) => {
    submitButton.disabled = !state.isComplete || !state.isValid
  }
})

Read Mounted Card Values

After mount, you can read encrypted values from mountedCard.values.
if (mountedCard.isComplete && mountedCard.isValid) {
  const payload = mountedCard.values

  // Encrypted values
  console.log(payload.card.number)
  console.log(payload.card.cvv)

  // Additional metadata
  console.log(payload.card.brand)
  console.log(payload.card.last_digits)
}
Card number and CVV are encrypted before exposure in mountedCard.values.

Submit Pattern

const mountedCard = await cardElement.mount('#card-element')

async function submitCardCheckout() {
  if (!mountedCard.isComplete || !mountedCard.isValid) {
    return
  }

  await fetch('/api/checkout/card', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ cardData: mountedCard.values.card })
  })
}
Never collect raw PAN/CVC in your own inputs. Use only encrypted values from mountedCard.values.

Live Demo

This sandbox demo mounts the card element directly in Mintlify, applies colorScheme based on the current docs theme, and shows real-time state plus encrypted values.

Theming

theme accepts:
  • A preset: 'clean' | 'minimal' | 'material'
  • A config object: { preset?, styles?, fonts? }
  • A function theme: (utils) => ({ styles, fonts })

Preset Preview

This live sandbox preview uses tabs to switch between built-in presets while keeping the same card configuration. The iframe window stays on a light surface for easier visual comparison.

Custom Styling

Theme styles are CSS-in-JS rules injected into the secure iframe. You can style CSS selectors, but you cannot change iframe HTML structure.

Full Theme Example

rinne-theme.ts
export const checkoutTheme = {
  preset: 'minimal',
  fonts: ['https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap'],
  styles: {
    ':root': {
      colorScheme: 'light'
    },
    fieldset: {
      border: '1px solid #dbe2ea',
      borderRadius: '14px',
      backgroundColor: '#ffffff',
      padding: '14px'
    },
    label: {
      color: '#475569',
      fontSize: 12,
      fontWeight: 600,
      textTransform: 'uppercase',
      letterSpacing: '0.04em'
    },
    '.field input': {
      marginTop: 6,
      height: '48px',
      padding: '10px 12px',
      border: '1px solid #cbd5e1',
      borderRadius: '10px',
      backgroundColor: '#ffffff',
      color: '#0f172a',
      fontSize: 16
    },
    '.field[ev-name="number"] input': {
      letterSpacing: '0.08em',
      fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace'
    },
    '.field:focus-within input': {
      borderColor: '#2563eb',
      boxShadow: '0 0 0 3px rgba(37, 99, 235, 0.15)'
    },
    '.field[ev-valid="false"] input': {
      borderColor: '#dc2626'
    },
    '.field[ev-has-value="true"] input': {
      backgroundColor: '#f8fafc'
    },
    '.error': {
      marginTop: 6,
      fontSize: 12,
      color: '#dc2626'
    }
  }
}
const cardElement = await rinne.elements.card({
  colorScheme: 'light',
  theme: checkoutTheme,
  locale: 'pt',
  fields: ['number', 'expiry', 'cvc']
})

State Attributes for Selectors

Use these iframe attributes for state-aware styling:
ScopeAttributeValues
Root fieldsetev-componentCard
Root fieldsetev-validtrue or false
Field wrapper .fieldev-namename, number, expiry, cvc
Field wrapper .fieldev-validtrue or false
Field wrapper .fieldev-has-valuetrue or false

Responsive Styling

Use a function theme and utils.media() so breakpoints match the parent document viewport.
const responsiveTheme = (utils) => ({
  fonts: ['https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap'],
  styles: {
    label: {
      fontSize: 12
    },
    '.field input': {
      height: '48px',
      padding: '10px 12px',
      fontSize: 16
    },
    '.field[ev-name="number"] input': {
      letterSpacing: '0.08em'
    },
    ...utils.media('(max-width: 768px)', {
      '.field input': {
        height: '44px',
        fontSize: 15
      }
    }),
    ...utils.media('(max-width: 480px)', {
      label: {
        fontSize: 11
      },
      '.field input': {
        height: '40px',
        padding: '8px 10px',
        fontSize: 14
      },
      '.field[ev-name="number"] input': {
        letterSpacing: '0.05em'
      }
    })
  }
})
const cardElement = await rinne.elements.card({
  colorScheme: document.documentElement.classList.contains('dark') ? 'dark' : 'light',
  theme: responsiveTheme
})
Set both colorScheme and theme using the same page theme source so iframe and host container stay visually consistent.

Mount Errors

mount() throws when the target selector is invalid or the provider cannot render the component.
try {
  const card = await rinne.elements.card()
  await card.mount('#missing-card-slot')
} catch (error) {
  console.error('Card mount failed', error)
}

Unmount

mountedCard.unmount()