appearance option of @payrails/web-sdk: the option types,
rule semantics, and the class-name contract — which CSS classes and state
modifiers the SDK guarantees on the DOM.
Types
All types are exported from@payrails/web-sdk.
Appearance
AppearanceRules
@media …, @supports …) the value is a nested AppearanceRules
map.
CardFormAppearance
Appearance under a named
key. The card payment button is a sibling of the card form, not a child — it has
no slot here.
DropinAppearance
rules apply across the drop-in; each building block listed above takes
its own appearance under a matching key. The exported interface also declares
reserved keys (leanButton, orderSummary, billingAddressForm,
termsAndConditions, the wallet buttons) and an open index signature — keys not
listed above are accepted by the compiler but currently have no effect. The
provider-drawn wallet buttons (Google Pay, Apple Pay, PayPal) are not stylable
through appearance — their chrome is configured via
paymentMethodsConfiguration.<method>.styles
Where each shape is accepted
Rule semantics
Selectors. Any selector the browser supports is valid: class selectors, state modifiers, comma lists, descendant combinators, pseudo-classes (:hover,
:focus, :focus-visible, :disabled, :autofill), pseudo-elements
(::placeholder, ::selection), and nested @media / @supports at-rules.
Properties. Any CSS property is valid. Write property names in camelCase
(boxShadow) or kebab-case (box-shadow) — camelCase is converted on emission.
Custom properties (--my-var) pass through unchanged. Values are strings or
numbers.
No validation. The SDK emits your rules verbatim as CSS and does not
currently validate selectors or declarations; anything the browser cannot parse
is silently ignored by the browser, not reported by the SDK.
Scoping. Each element’s rules apply only within that element’s own DOM
subtree. A rule passed to one card form does not affect another card form or any
other widget. Write selectors flat (.payrails-input, not a
descendant-of-the-widget path) — the SDK scopes them for you. Known limitation
in 6.0.0-RC: on the host page, scoped rules match descendants of the widget but
not the widget’s own root node — for widgets that render a single node (the pay
buttons), host-page rules currently have no effect. Rules applied inside the
secure iframes (card form, collect elements) are not affected.
Cascade. SDK default styles live in the CSS layer payrails-defaults; your
rules are emitted into the layer payrails-appearance, which is declared after
it — your rules always beat the SDK defaults regardless of selector specificity.
Rules in your own external stylesheets are unlayered and beat both. In browsers
without CSS layer support (Safari before 15.4), the SDK’s default styles are not
applied; your rules still are.
Composition. When a container and one of its children both style the same
node, the child’s rules win; the container’s rules fill in whatever the child
did not set.
Secure fields. Card data inputs render inside Payrails-hosted iframes. The
root rules of a card form or collect element are forwarded into the iframe and
applied there in addition to the host page; nested child keys (installments,
address, brandSelector) apply outside the iframe only.
The class-name contract
The SDK stamps two kinds of class names:- Generic classes (
.payrails-input,.payrails-button, …) mark the UI primitives and are listed below. They are the public styling surface, stable within a major version. - Widget-specific classes (for example
.payrails-card-form) are internal. They can change in any release and are not part of this contract.
Generic classes
State modifiers
State is expressed as BEM modifiers on the generic classes:
On secure card fields,
--invalid and --valid are removed while the field has
focus, so a shopper correcting a value does not see the error state; they are
re-evaluated on blur. A selector like .payrails-input--invalid:focus therefore
never matches a secure field. For a persistent invalid look on touched fields,
combine with --dirty:
--empty and --dirty exist on secure card fields only. Inputs in
schema-driven forms (dynamicElement, the billing address form) keep
--invalid / --valid while focused.
Per-element contract
The tables below list which generic classes and modifiers appear in each element’s DOM. Native pseudo-classes apply everywhere they are valid.Card form — cardForm({ appearance })
Nested widget slots on
CardFormAppearance:
Collect element (secure field) — createCollectElement({ appearance })
Rules apply inside the field’s iframe.
Card payment button — paymentButton({ appearance })
Generic redirect button — genericRedirectButton({ appearance })
For Revolut Pay the button renders Revolut’s own artwork configured via
revolutOptions; appearance does not reach that artwork.
Lean button — leanButton({ appearance })
The Lean button does not use
--disabled. The Lean-hosted bank dialog is themed
via dialogCustomization, not appearance.
Card list — cardList({ appearance })
Dynamic form — dynamicElement({ appearance })
Also the shape of the drop-in’s billing address form.
Drop-in — dropin({ appearance })
Root rules apply across the drop-in’s blocks — for example .payrails-button
under root rules reaches every pay button the drop-in renders, and
.payrails-container reaches each block’s wrapper. Each slot scopes its rules
to that block:
authSuccess also styles the payment-pending screen, which reuses the success
screen’s markup. The other keys declared on DropinAppearance (leanButton,
orderSummary, billingAddressForm, termsAndConditions, the wallet buttons)
are reserved and currently have no effect.