Skip to content

feat: add per-widget appearance override support for payment elements - #1715

Open
aritro2002 wants to merge 2 commits into
mainfrom
appearance-modification
Open

feat: add per-widget appearance override support for payment elements#1715
aritro2002 wants to merge 2 commits into
mainfrom
appearance-modification

Conversation

@aritro2002

Copy link
Copy Markdown
Contributor

Type of Change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring
  • Dependency updates
  • Documentation
  • CI/CD

Description

Adds support for passing a per-widget appearance object when creating
payment elements, allowing widget-level appearance to override the
global appearance set on HyperElements.

Changes

  • src/Utilities/Utils.res: Added resolveWidgetAppearance utility
    that picks the per-widget appearance from newOptions if present
    and non-empty, otherwise falls back to the elements-level appearance.
  • src/hyper-loader/Elements.res: Use resolveWidgetAppearance in
    create() so widgets can carry their own appearance into widgetOptions.
  • src/hyper-loader/PaymentMethodsManagementElements.res: Same fix
    applied to the payment methods management create() path.

How did you test it?

export const paymentElementOptions = (
  layout,
  extraOptions,
  // , appearance
) => ({
  displayDefaultSavedPaymentIcon: false,
  ...(layout && { layout }),
  wallets: {
    walletReturnUrl: window.location.origin,
    applePay: "auto",
    googlePay: "auto",
    style: {
      theme: "dark",
      type: "default",
      height: 55,
    },
  },
  appearance: {
    theme: "default",
    labels: "above",
    innerLayout: "spaced",
    componentType: "payment",
    variables: {
      fontFamily: "",
      fontSizeBase: "1rem",
      colorPrimary: "#23366f",
      colorBackground: "#ffffff",
      colorText: "#545454",
      colorDanger: "#fd1717",
      colorDangerText: "#fd1717",
      borderRadius: "4px",
      fontVariantLigatures: "",
      fontVariationSettings: "",
      spacingUnit: "11px",
      fontWeightLight: "400",
      fontWeightNormal: "500",
      fontWeightMedium: "600",
      fontWeightBold: "700",
      fontLineHeight: "",
      fontSize2Xl: "24px",
      fontSizeXl: "16px",
      fontSizeLg: "14px",
      fontSizeSm: "12px",
      fontSizeXs: "10px",
      fontSize2Xs: "8px",
      fontSize3Xs: "6px",
      colorSuccess: "",
      colorWarning: "",
      colorPrimaryText: "#2167ae",
      colorBackgroundText: "",
      colorSuccessText: "",
      colorWarningText: "",
      colorTextSecondary: "#23366f",
      colorTextPlaceholder: "",
      spacingTab: "12px",
      borderColor: "#e6e6e6",
      spacingAccordionItem: "10px",
      colorIconCardCvc: "",
      colorIconCardCvcError: "#fd1717",
      colorIconCardError: "#fd1717",
      spacingGridColumn: "20px",
      spacingGridRow: "20px",
      buttonBackgroundColor: "#2167ae",
      buttonHeight: "48px",
      buttonWidth: "100%",
      buttonBorderRadius: "6px",
      buttonBorderColor: "#ffffff",
      buttonTextColor: "#ffffff",
      buttonTextFontSize: "16px",
      buttonTextFontWeight: "500",
      buttonBorderWidth: "0px",
    },
    rules: {
      ".Tab": {
        border: "1px solid #e6e6e6",
        borderRadius: "4px",
        backgroundColor: "#ffffff",
        color: "#23366f",
        alignItems: "start",
        transition:
          "background .15s ease, border .15s ease, box-shadow .15s ease",
        boxShadow: "0px 1px 1px rgb(0 0 0 / 3%), 0px 3px 6px rgb(0 0 0 / 2%)",
      },
      ".Tab:hover": {
        color: "#545454",
      },
      ".Label": {
        color: "#545454",
        opacity: "10",
        textAlign: "left",
      },
      ".Tab--selected": {
        color: "#23366f",
        boxShadow:
          "0px 1px 1px rgba(0, 0, 0, 0.03), 0px 3px 6px rgba(18, 42, 66, 0.02), 0 0 0 2px #23366f",
      },
      ".Tab--selected:hover": {
        border: "1px solid #23366f",
        color: "#23366f",
        boxShadow:
          "0px 1px 1px rgba(0, 0, 0, 0.03), 0px 3px 6px rgba(18, 42, 66, 0.02), 0 0 0 2px #23366f",
      },
      ".Tab--selected:focus": {
        border: "2px solid #23366f",
        borderColor: "#23366f",
        boxShadow:
          "0 0 0 2px #23366f4c, 0 1px 1px 0 #ffffff, 0 0 0 1px #23366f4c",
      },
      ".TabMore:focus": {
        border: "1px solid #23366f",
        boxShadow: "#23366f4c 0px 0px 0px 3px",
      },
      ".TabMore": {
        border: "1px solid #e6e6e6",
      },
      ".Input": {
        border: "1px solid #e6e6e6",
        color: "#545454",
        fontWeight: "400",
        borderRadius: "4px",
        boxShadow: "0px 1px 1px rgb(0 0 0 / 3%), 0px 3px 6px rgb(0 0 0 / 2%)",
        transition:
          "background 0.15s ease, border 0.15s ease, box-shadow 0.15s ease, color 0.15s ease",
      },
      ".Input-Compressed": {
        border: "1px solid #e6e6e6",
        color: "#545454",
        fontWeight: "400",
        boxShadow: "0px 1px 1px rgb(0 0 0 / 3%), 0px 3px 6px rgb(0 0 0 / 2%)",
        transition:
          "background 0.15s ease, border 0.15s ease, box-shadow 0.15s ease, color 0.15s ease",
      },
      ".Input:-webkit-autofill": {
        transition: "background-color 5000s ease-in-out 0s",
        "-webkitTextFillColor": "#545454 !important",
      },
      ".Input:focus": {
        border: "1px solid #23366f",
        boxShadow: "#23366f4c 0px 0px 0px 3px",
      },
      ".Input-Compressed:focus": {
        border: "1px solid #23366f",
        boxShadow: "#23366f4c 0px 0px 0px 2px",
        position: "relative",
        zIndex: "2",
      },
      ".Input--invalid": {
        color: "#fd1717",
        border: "1px solid #fd1717",
        transition:
          "border 0.15s ease, box-shadow 0.15s ease, color 0.15s ease",
      },
      ".Input::placeholder": {
        fontWeight: "400",
        color: "",
      },
      ".TabLabel": {
        transition: "color .1s ease",
        textAlign: "start",
      },
      ".TabIcon": {
        transition: "color .1s ease",
      },
      ".Block": {
        backgroundColor: "#ffffff",
        borderRadius: "4px",
        border: "1px solid #e6e6e6",
      },
      ".BlockDivider": {
        border: "1px solid #e6e6e6",
      },
      ".AccordionItem": {
        backgroundColor: "#ffffff",
        color: "#23366f",
        transition: "height 1s ease",
        boxShadow: "0px 1px 1px rgb(0 0 0 / 3%), 0px 3px 6px rgb(0 0 0 / 2%)",
      },
      ".AccordionMore": {
        backgroundColor: "#ffffff",
        color: "#23366f",
        border: "1px solid #e6e6e6",
      },
      ".AccordionMore:hover": {
        color: "#545454",
      },
      ".AccordionItem:hover": {
        color: "#545454",
      },
      ".AccordionItem--selected": {
        color: "#23366f",
      },
      ".AccordionItem--selected:hover": {
        color: "#23366f",
      },
      ".AccordionItemLabel": {
        transition: "color .1s ease",
      },
      ".AccordionItemLabel--selected": {
        color: "#23366f",
      },
      ".AccordionItemIcon--selected": {
        color: "#23366f",
      },
      ".PickerItem": {
        backgroundColor: "#ffffff",
        borderRadius: "4px",
        border: "1px solid #e6e6e6",
        color: "#23366f",
        padding: "11px",
        transition: "height 1s ease",
        boxShadow: "0px 1px 1px rgb(0 0 0 / 3%), 0px 3px 6px rgb(0 0 0 / 2%)",
      },
      ".PickerItem:hover": {
        color: "#545454",
      },
      ".PickerItem--selected": {
        color: "#23366f",
        border: "1px solid #23366f",
        boxShadow: "#23366f4c 0px 0px 0px 3px",
      },
      ".PickerItem--selected:hover": {
        color: "#23366f",
        border: "1px solid #23366f",
        boxShadow: "#23366f4c 0px 0px 0px 3px",
      },
      ".Checkbox": {
        fontWeight: "400",
        fontSize: "14px",
      },
      ".PaymentMethodsHeaderLabel": {
        color: "#545454",
        fontSize: "24px",
        fontWeight: "600",
        marginBottom: "1.5rem",
      },
    },
  },
  ...(extraOptions && extraOptions),
});

 <PaymentElement
                    id="payment-element"
                    options={paymentElementOptions(
                      parseLayoutParam(layoutQueryParam),
                      parseOptionsParam(optionsQueryParam),
                      // , parseAppearanceParam(appearanceQueryParam)
                    )}
                  />
                </div>
                <div>
                  <CardCVCElement
                    id="card-number-element"
                    options={{
                      displayDefaultSavedPaymentIcon: false,

                      wallets: {
                        walletReturnUrl: window.location.origin,
                        applePay: "auto",
                        googlePay: "auto",
                        style: {
                          theme: "dark",
                          type: "default",
                          height: 55,
                        },
                      },
                      appearance: {
                        theme: "bubblegum",
                        labels: "above",
                      },
                    }}
                  />
                </div>

Screen.Recording.2026-08-11.at.3.28.20.pm.mov

Checklist

  • I ran npm run re:build
  • I reviewed submitted code
  • I added unit tests for my changes where possible

@aritro2002 aritro2002 self-assigned this Aug 11, 2026
@aritro2002 aritro2002 added the Ready for Review PR with label Ready for Review should only be reviewed. label Aug 11, 2026
@semanticdiff-com

Copy link
Copy Markdown

Review changes with  SemanticDiff

@XyneSpaces

Copy link
Copy Markdown

[blocking] ReScript switch guard uses incorrect keyword

src/Utilities/Utils.res line 2112 uses | Object(obj) if obj->Dict.keysToArray->Array.length > 0 => .... ReScript pattern guards require the when keyword, so this is a syntax error and will fail compilation. Replace if with when:

| Object(obj) when obj->Dict.keysToArray->Array.length > 0 => perWidget

Comment thread src/Utilities/Utils.res Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for Review PR with label Ready for Review should only be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add per-widget appearance override support for payment elements

3 participants