0

I'd like to style my Stripe.js elements (card number, Cvc, Expiry) with floating labels, to be consistent with my other bootstrap floating labels I'm using ngx-stripe as a wrapper around Stripe.js. I've been able to make them look similar to my other bootstrap elements, but they don't float when focus is given. Stackblitz here.

Stripe has some info on floating label appearances here, but after attempting different things, nothing changed! So I might not be using it correctly or my bootstrap classes might be interfering with the Stripe CSS?

  1. https://stripe.com/docs/elements/appearance-api?platform=web#form-inputs---floating-labels
  2. https://stripe.com/docs/elements/appearance-api?platform=web#others

Here is what my card number element looks like using bootstrap's floating classes:

<div class="form-floating mb-3">
  <div id="floatingNumber" class="form-control stripe-form-control" placeholder="ex. 1111 1111 1111 1111"></div>
  <label for="floatingNumber">Card Number</label>
</div>

Image from my Stackblitz

enter image description here

You can see the positions of the label and text are fixed, they don't float.

Here is my Stackblitz that has it set up. Here is what I tried.

  1. Added bootstrap floating classes
  2. When creating the Stripe elements I added appearance: { labels: 'floating' } in the appearance
  3. Added "stripe-form-control" as a class - found somewhere on Stripe website
chuckd
  • 13,460
  • 29
  • 152
  • 331

2 Answers2

1

I'm not an expert in angular, bootstrap, or stripejs, but I just read the documentations.

Firstly, in order for bootstrap floating labels to work, it requires the <input> and <label> to be siblings, because the floating label will work because of :placeholder-shown pseudo element. The problem here is that your mounted stripe elements aren't an input, they became an input wrapped by an iframe, so bootstrap floating labels won't work without some tweak.

Secondly, you mentioned the stripe API documentation that says that we can use { labels: 'floating' }, and after trying this, it turns out that it only works for elements that are mounted as a pack (not sure what it's called), one of that elements is address, and it works because the label is inside the iframe (I attach the proof on the Stackblitz link down below). And then, in order to use a label on a mounted element, we must use this method. Unfortunately, that method didn't make the label injected inside the iframe, it's still outside, so the floating labels from the appearance API won't work.

Despite being inside the iframe, thankfully, the inputs can still change the class outside the iframe (it could give StripeElement--focus, StripeElement--complete, and StripeElement--invalid), so we could just imitate the behavior of the bootstrap floating labels and implement it to our custom code, by copying the bootstrap behavior.

The easiest solution I could think of is by adding this CSS:

...
/* reset the original behavior of the floating labels for #custom-inputs */
#custom-inputs .form-floating > .form-control:focus ~ label,
#custom-inputs .form-floating > .form-control:not(:placeholder-shown) ~ label {
  opacity: 1;
  transform: scale(1) translateY(0) translateX(0);
}

/* mimic the behavior of bootstrap floating labels for #custom-inputs */
#custom-inputs .form-floating > .form-control.StripeElement--focus ~ label,
#custom-inputs .form-floating > .form-control.StripeElement--complete ~ label,
#custom-inputs .form-floating > .form-control.StripeElement--invalid ~ label {
  opacity: 0.8;
  transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);
}

#custom-inputs .StripeElement--focus {
  color: #212529;
  background-color: #fff;
  border-color: #86b7fe;
  outline: 0;
  box-shadow: 0 0 0 0.25rem rgb(13 110 253 / 25%);
}
...

And then empty the placeholder (because we want to use the floating labels):

...
      if (!this.cardNumber) {
        this.cardNumber = this.elements.create('cardNumber', {
          ...this.emptyOptions,
          placeholder: '',
        });
        this.cardNumber.mount('#floatingNumber');
      }
      if (!this.cardCvc) {
        this.cardCvc = this.elements.create('cardCvc', {
          ...this.emptyOptions,
          placeholder: '',
        });
        this.cardCvc.mount('#floatingCvc');
      }
      if (!this.cardExpiry) {
        this.cardExpiry = this.elements.create('cardExpiry', {
          ...this.emptyOptions,
          placeholder: '',
        });
        this.cardExpiry.mount('#floatingExpiry');
      }
...

Here is the working altered Stackblitz

Damzaky
  • 6,073
  • 2
  • 11
  • 16
  • Hi, thanks for the help. I was able to add a .on('focus') and .on('blur') events to the card number element so that I could add and remove a placeholder. I forked the Stackblitz here - https://stackblitz.com/edit/angular-zahire?file=src/test/stripe/stripe.component.ts – chuckd Mar 07 '23 at 20:45
  • Just one question. How did you get the packed element "Address" to style like bootstrap? I'm having trouble figuring that out from the CSS and other code in your Stackblitz. I see how the 3 custom elements are styled from 'form-floating' but "Address" doesn't have that class! – chuckd Mar 07 '23 at 20:48
  • @user1186050 that one is the special packed element I mentioned, it comes with labels inside the iframe (it doesn't use your bootstrap or our CSS, it might use bootstrap inside the iframe, it might be a coincidence how it looks like the bootstrap you have), it's enabled because of the appearance API `labels: "floating"`. I got the code from https://stripe.com/docs/elements/address-element/collect-addresses?platform=web – Damzaky Mar 07 '23 at 21:57
  • Oh ok. I'll give you the points. But I have another issue related to HTML and the Stripe library. Maybe you can help me with some insight on what's going on? In my local app, I had to remove the custom mounted elements from it because something was happening under the hood using a form Stepper called Angular2-wizard. If I can figure out what was causing the error, I can revert back to using custom mounted elements with Stripe, like in this post, otherwise I'll have to use the built in components from the npm ngx-stripe. – chuckd Mar 07 '23 at 22:07
  • Here is the a link to my other post. https://stackoverflow.com/q/75656091/1186050 and another post I created trying to set up this npm library in Stackblitz, but had errors trying to install and reference the form stepper. https://stackoverflow.com/q/75632961/1186050 – chuckd Mar 07 '23 at 22:08
  • Well I almost never touched angular code hahah, I'll see what I can do – Damzaky Mar 07 '23 at 23:46
-1

I have changed your code a little bit, set input instead div. Are you trying to do that? https://stackblitz.com/edit/angular-kauqxz?file=src%2Ftest%2Fstripe%2Fstripe.component.css,src%2Ftest%2Fstripe%2Fstripe.component.html

it works with a name input, bc for name you have an input, not a div tag

   <h5>Working Floating name</h5>
   <div class="form-floating mb-3">
      <input type="text" class="form-control" id="floatingInput" formControlName="name" placeholder="Brad Pitt">
      <label for="floatingInput">Name</label>
   </div>

here you can see that this style you are trying to use includes :focus pseudoclass and it doesn't work with div

.form-floating>.form-control-plaintext~label, .form-floating>.form-control:focus~label, .form-floating>.form-control:not(:placeholder-shown)~label, .form-floating>.form-select~label {
    opacity: .65;
    transform: scale(.85) translateY(-0.5rem) translateX(0.15rem);
}

div:focus -> it doesn't work

input:focus -> it works

  • Hi, where in your stackblitz link is the css? FYI - An input can't be used here as these elements part of the Stripe.js library and these custom elements require a div, not an input. – chuckd Mar 07 '23 at 22:14
  • oh sorry, didn't know that. is that possible to add element in that case somewhere under – lastcallofsummer Mar 07 '23 at 22:26
  • Above, not under – lastcallofsummer Mar 07 '23 at 22:48
  • um IDK, it might be possible, but will try and avoid hacking it too much as it could potentially lead to other issues. – chuckd Mar 07 '23 at 23:24
  • If you want to use this approach, you'll need to engineer the `:placeholder-shown` pseudo element as well (which I think is pretty hard), and if you plan to use hidden input, it might defeat the purpose of using stripe iframe in the first place (iframe is for security), just fyi – Damzaky Mar 07 '23 at 23:49