Input OTP
Four ways to break six digits into groups, each valid or invalid, plus the single slot on its own with the five states that belong to one box rather than to the whole code.
Three of the four layouts butt their slots together, so the group carries a single base.input outline and 1px dividers sit absolutely between the boxes. Give every slot its own border and each seam would draw two lines instead of one.
Six separate 32 × 32 boxes, 8 apart, each with its own outline and radius.sm corners. It is the only layout where the slot is a whole shape on its own, which is also why it is the widest at 232.
Not just the outline: the dividers between slots and the dashes between groups all go to base.destructive too. A code that is wrong is wrong across its whole width, and leaving the seams grey would read as a partly-valid field.
One box, five states, 32 square
Published separately because the states belong to a single digit rather than to the whole code: one slot is focused while the others are not, one is filled while the rest wait.
A verification screen, a rejection, and a code ready to go
1000 by 881, three sections. The card carries the source's own copy. The last section is the only place in the file that fills a code in, because a joined strip cannot take digits without detaching it from the component.
Two components, thirteen variants
Read straight out of the Penpot file. The axes below are the property names you will see in the variant dropdown.
Using the Input OTP
Because butted boxes each carrying their own 1px outline would draw two lines at every seam. Three of the four layouts put a single base.input outline on the group and drop 1px dividers between the slots as absolute children, so every seam is exactly one line wide.
It is the only layout where a slot is a whole shape: six separate 32 × 32 boxes, 8 apart, each with its own outline and radius.sm corners. No group, no dividers. It is also the widest at 232 against Digits Only's 192.
Everything structural. The group outline, the dividers between slots and the dashes between groups all go to base.destructive. Leaving the seams grey would make a wrong code look partly valid, which is worse than not flagging it at all.
Because its states belong to one digit, not to the code. One slot is focused while the others wait, one is filled while the rest are empty. Those five states could not be an axis on InputOTP without multiplying eight variants by five for combinations nobody needs.
Three sections at 1000 wide. A verify-your-login card carrying the source's own copy, a code that did not match with the message under it, and a six-digit code filled in with a Verify button. The last one is the only place in the file where the Slot component's Filled state is used six times over, because the joined layouts cannot take digits without detaching.