JSFiddle - React, Tailwind, and code Playground

by Fabio Dan

HTML

<main>
  <div class="postcode-field">
    <label class="postcode-field__label">Postcode</label>
    <input placeholder="Sector or unit" class="postcode-field__input" type="text" maxlength="8" pattern="[a-zA-Z]{1,2}[0-9][0-9a-zA-Z]?\s?[0-9]|[a-zA-Z]{1,2}[0-9][0-9a-zA-Z]?\s?[0-9][a-zA-Z]{2}"><br>
    <div class="postcode-field__message">
      Sector or unit invalid
    </div>
    <div class="postcode-field__coverage">
      Coverage: <span class="postcode-field__coverage__value"></span>
    </div>
  </div>

  <div class="postcode-samples">
    <h4 class="postcode-samples__heading">Postcode variations</h4>
    <ul>
      <li>EC1A 1BB</li>
      <li>W1A 0AX</li>
      <li>M1 1AE</li>
      <li>B33 8TH</li>
      <li>CR2 6XH</li>
      <li>DN55 1PT</li>
    </ul>
  </div>
</main>

SCSS

html {
  box-sizing: border-box;
  height: 100%;
}

*, *::before, *::after {
  box-sizing: inherit;
}

body {
  font-family: "Roboto", "Helvetica Neue", sans-serif;
  color: #3d3d3d;
  display: flex;
  margin-top: 10%;
  justify-content: center;
  height: 100%;
}

main {
  display: inline-block;
}

.postcode-field {
  width: 255px;
  position: relative;
}

.postcode-field__message,
.postcode-field__coverage,
.postcode-field__label {
  display: block;
  font-size: 12px;
  color: #666666;
}

.postcode-field__label {
  padding-bottom: 5px;
 }

.postcode-field__label:after {
  content: " *";
  color: #CC0000;
}

.postcode-field__input {
  width: 100%;
  height: 34px;
  border: solid 1px #ccc;
  color: #3d3d3d;
  border-radius: 4px;
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
  padding: 0 12px;
  font-size: 14px;
  text-transform: uppercase;

  &::placeholder {
    color: #A9A9A9;
    text-transform: none;
  }
}

.postcode-field--validation:not(:focus):invalid {
    border-color: #CC0000;
    & ~ .postcode-field__message {
      display: block;
    }
    & ~ .postcode-field__coverage {
      display: none;
    }
 } 
 

.postcode-field__message,
.postcode-field__coverage {
  position: absolute;
  left: 0;
  bottom: -1;
}

.postcode-field__message {
  color: #CC0000;
  display: none;
}

.postcode-field__coverage {
  left: auto;
  right: 0;
}

.postcode-samples {
  margin-top: 80px;
  font-size: 14px;
}

.postcode-samples__heading {
  font-weight: 500;
}

JavaScript

const inputElem = document.querySelector('.postcode-field__input')
const coverageElem = document.querySelector('.postcode-field__coverage__value')

const regex = {
  whiteSpace: /\s/g,
  sector: /[a-zA-Z]{1,2}[0-9][0-9a-zA-Z]?\s?[0-9]/g,
  unit: /[a-zA-Z]{1,2}[0-9][0-9a-zA-Z]?\s?[0-9][a-zA-Z]{2}/g,
}

function formatPostCode(value) {
  const formattedValue = value.replace(/ /g, '').replace(/^(.*)(\d)/, "$1 $2")
  inputElem.value = formattedValue
}

function onPostCodeInput(e) {
  const value = e.target.value;

  if (value.match(regex.unit)) {
    setCoverage('Unit')
    formatPostCode(value)
  } else if (value.match(regex.sector)) {
    setCoverage('Sector')
    formatPostCode(value)
  } else {
    setCoverage('Not defined')
  }
}

function setCoverage(value) {
  coverageElem.innerHTML = value
}

function validate(e) {
	const value = e.target.value
  const isValid = regex.unit.test(value) || regex.unit.test(value)

	if (isValid) {
	  inputElem.classList.remove('postcode-field--validation')
  } else {
	  inputElem.classList.add('postcode-field--validation') 
  }
  
	return isValid
}

function init() {
  setCoverage('Not defined')
  inputElem.addEventListener("input", onPostCodeInput)
  inputElem.addEventListener('blur', validate)  
}

init();