Ways to Build a Search Box

Posted .

It occasionally feels good to sit down and churn out some simple UI / UX elements just for the heck of it. It can be a rather meditative experience.

While building these search box variants, I discovered an interesting gotcha regarding the required property on HTML inputs and how it responds on form submission. But I’ll touch on that minor detail toward the end of this article.

Some Search Boxes for Your Casual Viewing Pleasure

Basic Search

I started out by building the most basic search box possible. It is composed of an HTML input of type="search" and a button of type="submit" —all wrapped by a form tag. You should always wrap your search box with the form tag with an action property that has the URL that points to a “full results” page. However, because I coded this inside CodePen, I just assigned it a value of hash (#).

In addition, it is also best-practice to wrap the form tag with a section tag that has role="search". It’s simply an extra measure to keep your markup semantics clear and to avoid any possibility of confusing screen readers.

<!-- Basic Search -->
<div class="preview-zone">
  <h4>Basic Search</h4>
  <section class="search" role="search">
    <form action="#" method="get" class="search__form">
      <input id="basic-search" class="search__input" type="search" name="search" maxlength="100" placeholder="I am looking for..." required />
      <button class="search__btn-submit" type="submit">Search</button>
    </form>
  </section>
</div>

/**
 * Search styles
 */

// Colors
$input-color: #333;
$button-color: #777;
$icon-color: #666;

// Namespace
$ns: '.search';

#{$ns} {
  &__form {
    display: flex;
  }
  
  &__input,
  &__btn-submit {
    &:focus,
    &:active {
      outline: 0;
      box-shadow: 0 0 16px rgba(255, 255, 255, 0.5);
    }
  }

  &__input {
    padding: 15px;
    width: 300px;
    color: #eee;
    font-size: 14px;
    background-color: $input-color;
    border: 0;
  }
  
  &__btn-submit {
    position: relative;
    padding: 15px;
    color: #eee;
    font-size: 13px;
    background-color: #666;
    border: 0;
    text-transform: uppercase;
    letter-spacing: 1px;
    cursor: pointer;
    z-index: 10;
    transition: background-color 200ms ease-out;
    
    &:hover {
      background-color: $button-color;
    }
  }
}

basic-search

Also take notice of the added required property on the input element. If you click on the search button you’ll see a friendly message appear like in the screenshot below.

basic-search-required

The appearance of these generic messages depends on your browser. Please keep in mind that you’ll also want to handle checking for empty inputs with JavaScript as an extra safeguard for those using old browsers.

Large Search

Here, I created a new modifier class search--large that makes the search larger by increasing the padding for both the input field and the search button.

<!-- Large Search -->
<div class="preview-zone">
  <h4>Large Search</h4>
  <section class="search search--large" role="search">
    <form action="#" method="get" class="search__form">
      <input id="large-search" class="search__input" type="search" name="search" maxlength="100" placeholder="I am looking for..." required />
      <button class="search__btn-submit" type="submit">Search</button>
    </form>
  </section>
</div>

// Large modifier
&--large {
  #{$ns}__input {
    padding: 30px;
  }

  #{$ns}__btn-submit {
    padding: 30px;
  }
}

large-search

Swapped Search

This is an example of how you could easily reorder elements by using the CSS flexbox order property. .search__form must have CSS property display: flex; for this to work.

<!-- Swapped Search -->
<div class="preview-zone">
  <h4>Swapped Search</h4>
  <section class="search search--swap" role="search">
    <form action="#" method="get" class="search__form">
      <input id="swap-search" class="search__input" type="search" name="search" maxlength="100" placeholder="I am looking for..." required />
      <button class="search__btn-submit" type="submit">Search</button>
    </form>
  </section>
</div>

// Swapped modifier
&--swap {
  #{$ns}__input {
    order: 1;
  }
}

swapped-search

Large & Swapped Search

This example combines both modifier classes search--large and search--swap.

<!-- Large & Swapped Search -->
<div class="preview-zone">
  <h4>Large &amp; Swapped Search</h4>
  <section class="search search--large search--swap" role="search">
    <form action="#" method="get" class="search__form">
      <input id="large-swap-search" class="search__input" type="search" name="search" maxlength="100" placeholder="I am looking for..." required />
      <button class="search__btn-submit" type="submit">Search</button>
    </form>
  </section>
</div>

large-swapped-search

Icon Search

Don’t like the boring ol’ search button? Well, here’s an example using a nifty search icon that is all built with CSS.

<!-- Icon Search -->
<div class="preview-zone">
  <h4>Icon Search</h4>
  <section class="search search--icon" role="search">
    <form action="#" method="get" class="search__form">
      <input id="icon-search" class="search__input" type="search" name="search" maxlength="100" placeholder="I am looking for..." required />
      <button class="search__btn-submit" type="submit"></button>
    </form>
  </section>
</div>

// Icon modifier
&--icon {
  #{$ns}__form {
    position: relative;
  }

  #{$ns}__btn-submit {
    position: absolute;
    right: 0;
    top: 0;
    padding: 0;
    width: 46px;
    height: 100%;
    background: none;

    &:focus,
    &:active,
    &:hover {
	  box-shadow: none;

      &:before {
	    border-color: #eee;
	  }

	  &:after {
	    background-color: #eee;
	  }
    }

    &:before,
    &:after {
	  content: '';
	  position: absolute;
	  transition: background-color 200ms ease-out,
				  border-color 200ms ease-out;
    }

    &:before {
	  right: 8px;
	  top: 8px;
	  width: 16px;
	  height: 16px;
	  border: 3px solid $icon-color;
	  border-radius: 100%;
    }

    &:after {
	  right: 30px;
	  top: 23px;
	  width: 3px;
	  height: 16px;
	  background-color: $icon-color;
	  transform: rotate(45deg);
    }

    &:hover {
	  background: none;
    }
  }
}

icon-search

Icon Search (Expandable)

This is where things get a tad more interesting. In at least Chrome and Firefox, simply clicking the search icon will cause the input field to expand —without JavaScript! The required attribute will actually focus the input when the message is triggered. Our CSS tells the input to expand when in a :focus or :active state by setting the scaleX to 1.

Great, we got away with triggering an animation upon user interaction without JavaScript.

But wait…

Nope! Now, if you’re on a Mac, trying clicking on the search icon in Safari. Absolutely nothing happens. Why? Well, it turns out that the required attribute is not supported by Safari. So now we know that we must use JavaScript to trigger the expansion animation.

<!-- Icon Search (Expandable) -->
<div class="preview-zone">
  <h4>Icon Search (Expandable)</h4>
  <section class="search search--icon search--icon-expandable" role="search">
    <form action="#" method="get" class="search__form">
      <input id="icon-search-expandable" class="search__input" type="search" name="search" maxlength="100" placeholder="I am looking for..." required />
      <button class="search__btn-submit" type="submit"></button>
    </form>
  </section>
</div>

// Icon (Expandable) modifier
&--icon-expandable {
  #{$ns}__input {
    transform: scaleX(0);
    transform-origin: right center;
    transition: transform 300ms ease-out;

    &:focus,
    &:active {
	  transform: scaleX(1);
    }
  }

  #{$ns}__btn-submit {
    background-color: $input-color;

    &:hover {
	  background-color: $input-color;
    }
  }
}

search-expandable

Icon Search (Expandable With JS)

This solution will work in all modern browsers. We are now using JavaScript to handle the toggle between expanded and not expanded states. In this solution, I actually store that value in a data attribute.

<!-- Icon Search (Expandable with JS) -->
<div class="preview-zone">
  <h4>Icon Search (Expandable with JS)</h4>
  <section id="expandable-search" class="search search--icon search--icon-expandable" role="search">
    <form action="#" method="get" class="search__form">
      <input id="icon-search-expandable-js" class="search__input" type="search" name="search" maxlength="100" placeholder="I am looking for..." />
      <button class="search__btn-submit" type="submit"></button>
    </form>
  </section>
</div>

&--expanded {
  #{$ns}__input {
    transform: scaleX(1);
  }
}

// Cache some selectors
const $expandableSearch = document.getElementById('expandable-search')
const $expandableSearchBtn = $expandableSearch.querySelector('.search__btn-submit')
const $expandableSearchInput = $expandableSearch.querySelector('.search__input')

// Attach click event handler on search icon
$expandableSearchBtn.addEventListener('click', function (e) {

  e.preventDefault()

  const expanded = $expandableSearch.getAttribute('data-expanded')

  // Toggle expanded state class and store in data attribute
  if (expanded === null || expanded === 'false') {
    $expandableSearch.setAttribute('data-expanded', 'true')
    $expandableSearch.classList.add('search--expanded')
    $expandableSearchInput.focus()
  } else {
    $expandableSearch.setAttribute('data-expanded', 'false')
    $expandableSearch.classList.remove('search--expanded')
  }
})

I hope you enjoyed looking at these examples. I have so kindly embedded a CodePen with live examples of all the search boxes that were pictured above.

See the Pen Cool Search Boxes by Keenan Staffieri (@keenode) on CodePen.