Navigation

v0.0.4
Forms

Keypad

The keys of a phone, for a code or an amount typed with a thumb.

On this page

A keypad is the keys of a phone, for a code or an amount typed with a thumb: twelve buttons in three columns. A key is a raised face that sinks at once under a press, with a large figure and, under it, the small letters a phone prints there. It types nothing by itself. Each key is a button, and the page puts its figure where it belongs.

Keypad

<input class="input input-lg text-center w-full max-w(272)" id="pad-amount" type="text" inputmode="none" value placeholder="0" aria-label="Amount" readonly><div class="keypad" id="pad" role="group" aria-label="Keypad">  <button class="keypad-key" type="button" value="1">1</button>  <button class="keypad-key" type="button" value="2">2    <span class="keypad-letters">abc</span>  </button>  <button class="keypad-key" type="button" value="3">3    <span class="keypad-letters">def</span>  </button>  <button class="keypad-key" type="button" value="4">4    <span class="keypad-letters">ghi</span>  </button>  <button class="keypad-key" type="button" value="5">5    <span class="keypad-letters">jkl</span>  </button>  <button class="keypad-key" type="button" value="6">6    <span class="keypad-letters">mno</span>  </button>  <button class="keypad-key" type="button" value="7">7    <span class="keypad-letters">pqrs</span>  </button>  <button class="keypad-key" type="button" value="8">8    <span class="keypad-letters">tuv</span>  </button>  <button class="keypad-key" type="button" value="9">9    <span class="keypad-letters">wxyz</span>  </button>  <button class="keypad-key keypad-key-quiet" type="button" value=".">.</button>  <button class="keypad-key" type="button" value="0">0</button>  <button class="keypad-key keypad-key-quiet" type="button" value="delete" aria-label="Delete">    <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">      <path d="M9 6h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H9l-5-6zM12 10l4 4m0-4-4 4" />    </svg>  </button></div><script>  var amount = document.getElementById('pad-amount')  document.getElementById('pad').addEventListener('click', function (event) {    var key = event.target.closest('button')    if (!key) return    amount.value = key.value === 'delete' ? amount.value.slice(0, -1) : amount.value + key.value  })</script>
Press the keys: the script puts each figure in the field.

#A key

keypad-key is a button 52px tall. It has the raised face of a button and none of its travel: under a press it is in the well at once, with no transition, because a thumb entering six figures moves faster than one. Under a pointer it takes the firmer contour of a hovered control.

keypad-letters is the line under a figure. It is there because people remember codes as words; leave it out for an amount.

keypad-key-quiet is a key that is not a figure: delete, a decimal point, a sign. It has no face until it is pressed, so the ten figures read as the pad and the others as its margin. Where a corner of the pad has no key, put an empty <span> there to keep the grid.

#The pad

The pad is three equal columns 8px apart, as wide as its container up to 17rem. Two things it does are easy to forget when a pad is built from plain buttons:

  • It sets touch-action: manipulation, so the browser does not wait to see whether a second tap is a double-tap zoom. Two quick presses of the same key are two presses.
  • It does not select its figures, so a press that slides a little never highlights text.

--keypad-key sets the height of a key and --keypad-gap the space between keys.

#What the page does

The keypad has no behavior of its own, since what a key means belongs to the page: a figure appended to an amount, a slot of an OTP field filled, a call placed. One listener on the pad is enough, as in the example: read the pressed button's value and write it where it goes.

Keep a real field on the page. A reader with a hardware keyboard types into it, a password manager fills it, and a screen reader hears its value change. inputmode="none" on that field keeps the system's own keyboard from opening over the pad on a phone.

#Accessibility

  • Give the pad role="group" and an aria-label. Each key is a button whose name is its figure; a key that shows only an icon needs an aria-label.
  • The letters under a figure are part of the button's text. If "2 abc" is noise for your readers, wrap the letters in an element with aria-hidden="true".
  • Announce the result, not each press: the field that receives the figures is what a screen reader should follow.
  • Keys are 52px tall, above the 44px a touch target needs, at every pointer.

#Classes

ClassDescription
keypadThe pad: keys in three columns, 8px apart, 17rem wide at most. It never selects its figures and never zooms on a double tap
keypad-keyOne key, on a button: a raised face 52px tall with a large figure, which sinks at once under a press
keypad-key-quietA key that is not a figure, such as delete or a decimal point: no face until it is pressed. With keypad-key
keypad-lettersThe letters under a figure, small and quiet

#Custom properties

PropertyDefaultDescription
--keypad-key3.25remHeight of a key
--keypad-gap.5remSpace between keys

#Source

@master/ui/components/keypad.css
/* * Keypad: the keys of a phone, for a code or an amount typed with a thumb. * Twelve buttons in three columns. A key is a raised face that sinks at once under a press: there is no * wait and no lift, because a thumb moves faster than a transition. Its figure is large, and the letters a * phone prints under a figure are small and quiet. A key that is not a figure (delete, a decimal point) is * keypad-key-quiet: no face until it is pressed. * The pad never selects its figures and never zooms on a double tap, so two quick presses are two presses. * It types nothing by itself: each key is a button, and the page puts its figure where it belongs. * Geometry: keys are 52px tall, 8px apart, with a large control's corner; the pad is 17rem wide at most. */@layer components {  .keypad {    --keypad-gap: .5rem;    --keypad-key: 3.25rem;    display: grid;    grid-template-columns: repeat(3, minmax(0, 1fr));    gap: var(--keypad-gap);    width: 100%;    max-width: 17rem;    min-width: 0;    margin: 0;    padding: 0;    touch-action: manipulation;    user-select: none;    -webkit-user-select: none;    /* A key held down is a key held down: no callout rises over it. */    -webkit-touch-callout: none;  }  .keypad-key {    --icon-size: 1.25rem;    display: grid;    align-content: center;    justify-items: center;    gap: .1875rem;    min-width: 0;    height: var(--keypad-key);    margin: 0;    padding: 0;    border: 0;    border-radius: calc(var(--radius-control-lg) * var(--corner-scale));    corner-shape: var(--corner-shape);    @apply --surface-sheet;    color: var(--color-text-strong);    box-shadow: var(--shadow-raised);    font: var(--font-weight-control) 1.375rem/1 var(--font-family-sans);    font-feature-settings: inherit;    font-variant-numeric: tabular-nums;    appearance: none;    cursor: pointer;    -webkit-tap-highlight-color: transparent;    @media (hover: hover) {      &:hover:not(:disabled) {        box-shadow: var(--shadow-raised-hover);      }    }    /* Pressed: into the well, with no transition to wait for. */    &:active:not(:disabled) {      background: var(--color-surface-inset);      box-shadow: var(--shadow-pressed);    }    &:focus-visible {      @apply --focus-ring;    }    &:disabled {      background: var(--color-surface-inset);      color: var(--color-text-disabled);      box-shadow: var(--shadow-flat);      cursor: not-allowed;    }    /* Not a figure: no face of its own until it is pressed. */    &.keypad-key-quiet {      background: transparent;      color: var(--color-text-body);      box-shadow: none;      @media (hover: hover) {        &:hover:not(:disabled) {          background: var(--color-surface-chip);          box-shadow: none;        }      }      &:active:not(:disabled) {        background: var(--color-surface-inset);        box-shadow: var(--shadow-pressed);      }      &:disabled {        background: transparent;        box-shadow: none;      }    }    @media (forced-colors: active) {      outline: 1px solid ButtonText;      outline-offset: -1px;    }  }  /* The letters under a figure. */  .keypad-letters {    color: var(--color-text-muted);    font-weight: 400;    font-size: .5625rem;    letter-spacing: .12em;    line-height: 1;    text-indent: .12em;    text-transform: uppercase;  }}