Skip to content

Creditcard

<loomi-creditcard> — a flippable credit-card input. The front face holds the card number, cardholder name, and expiry; an edge button flips the card to its back to enter the CVC. The network logo (Visa, Mastercard, American Express, Discover, Diners Club, JCB, UnionPay, Maestro) is detected live from the number’s prefix, and a contactless-payment glyph sits in the front’s top-right corner.

Terminal window
npm install @loomidev/creditcard lit
import "@loomidev/creditcard";
<loomi-creditcard cardholder-name="Emma Reid"></loomi-creditcard>

Typing a number auto-detects and shows the matching network logo, auto-groups the digits (4-6-5 for Amex, 4-4-4-4 for most others), and caps the length to that network’s real card-number length.

Click the small round button on the card’s right edge to flip to the back and focus the CVC field (Amex shows 4 digits, every other network shows 3). The same button flips back; Escape while flipped does too.

<loomi-creditcard flipped></loomi-creditcard>
document.querySelector("loomi-creditcard").addEventListener("flip", (e) => {
console.log(e.detail.flipped);
});

Not form-associated by design — card data is sensitive and is typically handed to a payment provider’s tokenization SDK rather than posted through a plain HTML form. Read the structured value from the value getter, or listen for input/change:

const el = document.querySelector("loomi-creditcard");
el.addEventListener("change", (e) => {
const { number, numberDigits, cardholderName, expiryMonth, expiryYear, cvc, brand } = e.detail;
// hand off to your payment SDK
});
console.log(el.value); // same shape, read on demand

number, cardholder-name, expiry-month, expiry-year, and cvc are all plain attributes/properties. number accepts either raw digits or a masked saved-card value such as **** **** **** 4242:

<loomi-creditcard
cardholder-name="Emma Reid"
number="4242424242424242"
expiry-month="07"
expiry-year="28"
></loomi-creditcard>

Leave brand unset to auto-detect from number. Set it explicitly to override (e.g. a saved card where you already know the network but don’t want to show the full number):

<loomi-creditcard brand="visa" number="•••• •••• •••• 4242"></loomi-creditcard>

Like every loomi component, color picks the gradient from the shared palette and recolors instantly from plain page CSS — no rebuild:

<loomi-creditcard color="success"></loomi-creditcard>
:root {
--loomi-success-600: #15803d;
--loomi-success-700: #166534;
}

Set variant="outline" for a bare card silhouette instead of the full-color gradient face — a soft gray border with no background fill, for light or minimal UIs:

<loomi-creditcard variant="outline" cardholder-name="Emma Reid"></loomi-creditcard>

Use variant="inline" for the compact card-information layout: card number on the first row, expiry and CVC on the second row, with no cardholder-name field.

<loomi-creditcard variant="inline"></loomi-creditcard>

validate() checks that every field is complete (full-length number for the detected network, a non-blank name, a non-expired MM/YY, and a full-length CVC) when required is set, and returns whether it passed:

<loomi-creditcard
required
show-error-inline
error-message="Complete the card details to continue"
></loomi-creditcard>
<script type="module">
const el = document.querySelector("loomi-creditcard");
payButton.addEventListener("click", () => {
if (!el.validate()) return;
// proceed
});
</script>

For the library-wide baseline, see Foundations — Accessibility.

For the shared container and viewport rules, see Foundations — Responsive behavior.

  • Filled card faces keep brand accent gradients with --loomi-text-on-primary labels.

For theme activation, token overrides, and contrast guidance, see Foundations — Dark mode.

AttributeDefaultDescription
name(blank)Targeting class only (see LoomiElement) — not a form field, since this component doesn’t submit.
cardholder-name(blank)Name printed on the card.
number(blank)Card number, auto-grouped per network as the user types. Masked saved-card values like **** **** **** 4242 are preserved for edit screens.
expiry-month(blank)Two-digit month, "01""12".
expiry-year(blank)Two-digit year.
cvc(blank)Security code (3 digits, 4 for Amex).
brand(auto-detected)Force a network logo: visa, mastercard, amex, discover, diners, jcb, unionpay, maestro.
color"primary"Gradient color, from the shared loomi palette. Ignored in outline variant.
variant"gradient""gradient" for the full-color accent face, "outline" for a bare silhouette, or "inline" for card number, expiry, and CVC fields only.
locale(blank)Overrides the global locale for this instance.
flippedfalseShows the back face. (boolean)
disabledfalseDisables every field and the flip button. (boolean)
readonlyfalseRead-only fields. (boolean)
requiredfalseFails validate() while any field is incomplete. (boolean)
error-message(blank)Message shown when validation fails.
show-error-inlinefalseRender the error beneath the card. (boolean)
no-clearingfalseRemove the default bottom margin. (boolean)

Properties: value (read-only LoomiCreditcardValue), activeBrand (read-only, the currently displayed LoomiCardBrand). Methods: validate(). Parts: front, back, number, name, expiry, cvc, flip-button.

EventDescription
changeFired when the value is committed or changed.
inputFired while the value is edited.
loomi-flipFired when the visible card face changes.
<loomi-creditcard
cardholder-name="Emma Reid"
number="5555555555554444"
expiry-month="11"
expiry-year="27"
color="secondary"
required
></loomi-creditcard>

<loomi-creditcard> is a standard custom element, so the browser can use it in plain HTML, Blade, React, Vue, Angular, Svelte, Astro, and most other frameworks. The important beginner rule is: install the package, import it once before the tag is rendered, then write the Loomi tag in your template.

Run install commands from the app where you want to use this component. That means the folder that contains that app’s package.json. Do not run these install commands from packages/creditcard unless you are editing LoomiUI itself.

Terminal window
cd /path/to/your-app
npm install @loomidev/creditcard lit

If you are contributing to LoomiUI itself, first move to the top-level components folder. That is where the main package.json for all packages lives, and pnpm --filter ... commands should be run from there:

Terminal window
cd /path/to/your-copy-of-loomiui/components
pnpm --filter @loomidev/creditcard build
pnpm --filter @loomidev/creditcard typecheck

Use the CDN version for prototypes, documentation pages, or a quick reproduction. The import map tells the browser where to find Lit, which Loomi components use internally.

<script type="importmap">
{ "imports": { "lit": "https://esm.sh/lit@3.3.3", "lit/": "https://esm.sh/lit@3.3.3/" } }
</script>
<script type="module" src="https://esm.sh/@loomidev/creditcard"></script>
<loomi-creditcard cardholder-name="Emma Reid"></loomi-creditcard>

In Vite, Webpack, Parcel, Rollup, or a framework build pipeline, install the package and import it once in your main app JavaScript file. After that, you can use the Loomi tag anywhere in your app.

import "@loomidev/creditcard";

This component does not submit through a native <form> — read its value getter or listen for input/change instead (see “Reading the value” above).

Run the install command from your Laravel project root, then import the component in resources/js/app.js. If your project uses Laravel Vite, npm run dev and npm run build should also be run from the Laravel project root.

Terminal window
cd /path/to/your-laravel-app
npm install @loomidev/creditcard lit
npm run dev
resources/js/app.js
import "@loomidev/creditcard";
<loomi-creditcard cardholder-name="{{ $name }}"></loomi-creditcard>

React can render Loomi tags directly. If you are on React 18, or if you need to pass arrays, objects, or functions, use a ref and assign those values after the component mounts.

import "@loomidev/creditcard";
export function LoomiExample() {
return (
<loomi-creditcard cardholder-name="Emma Reid"></loomi-creditcard>
);
}

If TypeScript does not recognize the Loomi tag in JSX, add it to your app’s JSX type declarations.

Import the package in the component that uses it, or once in your main Vue file. Vue templates can use Loomi tags directly. For arrays, objects, or functions, pass the value as a JavaScript property instead of as plain text.

<script setup>
import "@loomidev/creditcard";
</script>
<template>
<loomi-creditcard cardholder-name="Emma Reid"></loomi-creditcard>
</template>

If Vue warns that the tag is an unknown component, configure compilerOptions.isCustomElement for tags that start with loomi- in your Vite or Vue config.

Import the package once and tell Angular to allow custom HTML tags with CUSTOM_ELEMENTS_SCHEMA. For NgModule apps, add the schema to the module instead of the standalone component.

app.component.ts
import { CUSTOM_ELEMENTS_SCHEMA, Component } from "@angular/core";
import "@loomidev/creditcard";
@Component({
selector: "app-root",
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<loomi-creditcard cardholder-name="Emma Reid"></loomi-creditcard>
`,
})
export class AppComponent {}

Svelte can import the package inside a component script. Astro can import it in the frontmatter of the page or layout where the tag appears.

<script>
import "@loomidev/creditcard";
</script>
<loomi-creditcard cardholder-name="Emma Reid"></loomi-creditcard>
---
import "@loomidev/creditcard";
---
<loomi-creditcard cardholder-name="Emma Reid"></loomi-creditcard>

Frameworks such as Next.js, Nuxt, SvelteKit, and Astro sometimes render HTML on the server before browser-only code runs. If your framework complains, move the Loomi import to client-side code. In Next.js, that usually means a component with "use client"; in Nuxt, it often means a .client.ts plugin.

  • @loomidev/core
  • @loomidev/theme