1

I have a form input in which I want to enter a four-digit number (verification code). My problem is that after entering the fourth number, the structure and order of the PIN-code breaks down. Because the text pointer goes to the fifth character while I have defined four characters.

Is there a way to solve this problem with pure CSS? Or at least with pure JavaScript?

.pinBox {
  display: inline-block;
  overflow: hidden;
  position: relative;
  direction: ltr;
  text-align: left;
}

.pinBox:before {
  position: absolute;
  left: 0;
  right: 0;
  content: '';
  pointer-events: none;
  display: block;
  height: 75px;
  width: 300px;
  background-image: url(https://i.stack.imgur.com/JbkZl.png);
}

.pinEntry {
  position: relative;
  padding: 16px 29px;
  font-family: courier, monospaced;
  font-size: xx-large;
  border: none;
  outline: none;
  width: 302px;
  letter-spacing: 55px;
  background-color: transparent;
  overflow: hidden;
  text-align: left;
  direction: ltr;
}
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.1/css/bootstrap.css"/>

<form role="form" method="POST" action="">
  <div class="row my-4">
    <div class="col-12 text-center">
      <div class="pinBox">
        <input class="pinEntry" name="token" type="text" maxlength="4" value="">
      </div>
    </div>
  </div>

  <div class="text-center">
    <button type="submit" class="btn btn-primary mt-2">submit</button>
  </div>
</form>

here is a jsfidde demo

isherwood
  • 58,414
  • 16
  • 114
  • 157
user2726957
  • 169
  • 1
  • 13
  • Does this answer your question? [CSS: Create iOS style Pin Password box](https://stackoverflow.com/questions/20114366/css-create-ios-style-pin-password-box) – pilchard Mar 06 '22 at 17:47
  • 1
    @pilchard thanks for the comment. It used several inputs ... my code has only one input so this is not my answer. – user2726957 Mar 06 '22 at 17:57

1 Answers1

6

After a bit of investigating I realized that strangely, by assigning overflow: hidden; to the parent element resulted in the input to hop/move contextual X position as soon the 4th value was inserted.

Solution:

  • Use CSS clip on the <input> element!
  • Assign the squared grid as the background-image of your parent element (no need to use ::before pseudo elements!)

.pinBox {
  --width: 296px;
  --height: 74px;
  --spacing: 47px;
  
  display: inline-block;
  position: relative;
  width: var(--width);
  height: var(--height);
  background-image: url(https://i.stack.imgur.com/JbkZl.png); 
}

.pinEntry {
  position: absolute;
  padding-left: 21px;
  font-family: courier, monospaced;
  font-size: var(--spacing);
  height: var(--height);
  letter-spacing: var(--spacing);
  background-color: transparent;
  border: 0;
  outline: none;
  clip: rect(0px, calc(var(--width) - 21px), var(--height), 0px);
}
<div class="pinBox">
  <input class="pinEntry" name="token" type=text maxlength=4 autocomplete=off >
</div>

One cons of the above is that: once all four values are inputted, and if the user clicks after the fourth value - the caret will not be visible since it's clipped (letter-spaced inside the 5th position). It you can live with it - good, but it's a bad UX/UI in my opinion.

Perhaps you can add to the above some really small JS that does:

  • IF the input has 4 values length - select all the text-value using myInput.select()

Example:

const ELS_pinEntry = document.querySelectorAll(".pinEntry");
const selectAllIfFull = (evt) => {
  const EL_input = evt.currentTarget;
  if (EL_input.value.length >= 4) EL_input.select();
};
ELS_pinEntry.forEach(el => {
  el.addEventListener("focusin", selectAllIfFull);
});
.pinBox {
  --width: 296px;
  --height: 74px;
  --spacing: 47px;
  
  display: inline-block;
  position: relative;
  width: var(--width);
  height: var(--height);
  background-image: url(https://i.stack.imgur.com/JbkZl.png); 
}

.pinEntry {
  position: absolute;
  padding-left: 21px;
  font-family: courier, monospaced;
  font-size: var(--spacing);
  height: var(--height);
  letter-spacing: var(--spacing);
  background-color: transparent;
  border: 0;
  outline: none;
  clip: rect(0px, calc(var(--width) - 21px), var(--height), 0px);
}
<div class="pinBox">
  <input class="pinEntry" name="token" type=text maxlength=4 autocomplete=off >
</div>

Another cons I would improve by using the above (and the original) idea is: A11Y (Accessibility).
The many users with bad or challenged sight should be able to see an input outline while they tab trough a website. For design reasons the above had to remove the CSS outline on the INPUT element. Which is a no-no.

Currently I have no other better/simpler idea but to use 4 different inputs and join their values into a single hidden one that will ultimately be submitted with the FORM.

Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • but even with that the curser twinking at the end . – Rman__ Mar 06 '22 at 19:10
  • 1
    @scriptydude as I said in my Answer... yes, the **blinking caret** should be a **must have** for UI/UX reasons (for which I provided a hacky JS solution) - but in that case the issue is `letter-spacing` which places the caret too far away inside the clipped area. Sadly we don't have any CSS rule for: `letter-spacing-before` – Roko C. Buljan Mar 06 '22 at 19:14
  • 1
    thanks. I test it in my project and it works well. I searched about it for days and did not find anything suitable. – user2726957 Mar 06 '22 at 21:33
  • @user2726957 you're very welcome – Roko C. Buljan Mar 07 '22 at 01:25
  • This approach has pretty bad UX for users who type the code wrong. It's probably better to use distinct inputs per digit – Zach Saucier May 10 '23 at 21:24
  • @ZachSaucier I agree with you. The numbers shifting and the caret misposition makes it overall a bad UX/UI. I'll rethink on this one. Four distinct inputs are also a pain to handle (perhaps not the editing of a wrong value, but backspacing values, pasting the PIN, handling maxlength, etc.) – Roko C. Buljan May 11 '23 at 06:38
  • Those do require extra implementation work, but you can get to a pretty good experience by handling those situations with JS. – Zach Saucier May 11 '23 at 13:26
  • @ZachSaucier Obviously *with JS* ;) – Roko C. Buljan May 11 '23 at 14:02