Drupal 11: Theming The Search API Search Input

A common request I see when theming Search API forms is to swap out the normal submit element with a magnifying glass icon. Performing this action isn't difficult, but it does require adding a couple of operations to add a suggestion so a custom template can be used.

When I set up a view to perform a search against a Search API index I normally create an exposed filter for the text content. Views shows this as a block that can be embedded into the site. The block, however, comes with a input element to act as the search button, and it isn't possible to inject SVG icons into input elements.

By changing the input element to a button we can then inject a small SVG of a magnifying glass or similar to act as the search button.

Swapping out this input element takes a couple of steps, and I although I have done this technique a few times I still need to dig into old code to figure out how I did it. So, I thought I would document it so I didn't have to go looking for the solution again.

In this article I will look at how we can use a combination of form alters and suggestion hooks to change the Search API form submit input to a button so that an SVG can be embedded inside.

Altering The Search Form

The first step (and perhaps the trickiest) is to alter the search form to add a couple of attributes to the search submit element.

If we add a theme suggestion alter hook for the input element, the element itself has no knowledge of the context that surrounds it. This makes it tricky to know that we are altering the correct element or even to inject a suggestion that would be unique for the search form.

The form alter hook, therefore, is used to inject an attribute into the form element so that we can read this in the suggestions hook. This gives is a bit of data we can identify and use in the suggestions hook.

Finding the correct form is a little tricky because the name of the search form differs between sites and is dependent on the view that has been set up to wrap the search results. I normally use the name of the view to do find the right form, but it is also possible to infer the correct form through other means.

The simplest mechanism to alter the correct form is just to use the name of the view in the hook. For example, if the view was called "content_search" then the form #id element would have the ID "views-exposed-form-content-search-search-page". We can use this information to alter the form in the following way (using the new OOP hooks mechanism).

namespace Drupal\mymodule\Hook;

use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;

class SearchFormHooks {

  use StringTranslationTrait;

  public const SEARCH_VIEW_NAME = 'content-search';

  /**
   * Implements hook_form_alter().
   */
  #[Hook('form_alter')]
  public function formAlter(&$form, FormStateInterface $form_state, $form_id) {
    if ($form_id === 'views_exposed_form' && $form['#id'] === 'views-exposed-form-' . self::SEARCH_VIEW_NAME . '-search-page') {
      // Convert the search input to a button.
      $form['actions']['submit']['#attributes']['data-twig-suggestion'] = 'search_results_submit';
      $form['actions']['submit']['#attributes']['class'][] = 'search-box__button';
      $form['actions']['submit']['#attributes']['aria-label'][] = $this->t("Search");
    }
  }

}

The name of the view has been added as a constant to assist in porting this hook to other sites (or search forms).

The search input element should now contain a data attribute called data-twig-suggestion.

<input data-twig-suggestion="search_results_submit" class="search-box__button button js-form-submit form-submit" aria-label="Search" data-drupal-selector="edit-submit-content-search" type="submit" id="edit-submit-content-search" value="Search">

Notice that we also inject a custom class and a aria-label attribute. The class is for general theming purposes, but the aria-label is critical for screen readers. What we are doing here is removing the label of the search and replacing it with an SVG icon. We therefore need to add this label to ensure that screen readers will still identify the button with the correct label.

An alternative mechanism to using the form ID might be to look at the action of the form, which is normally "/search" for the Search API system. For example, we could rewrite the hook like this.

if ($form_id === 'views_exposed_form' && $form['#action'] === '/search') {
  // Convert the search input to a button.
  $form['actions']['submit']['#attributes']['data-twig-suggestion'] = 'search_results_submit';
  $form['actions']['submit']['#attributes']['class'][] = 'search-box__button';
  $form['actions']['submit']['#attributes']['aria-label'][] = t("Search");
}

You may have to do some experimenting on your setup if the above steps don't produce results.

With that in place we can add our template suggestion hook.

Adding The Suggestions Hook And Template

The suggestion hook for the input element can now be focused to look for the data-twig-suggestion attribute in the attributes array. If this is present then we know that we have injected this and can use this in our suggestion. Instead of throwing the attribute away, we pass this along to the suggestion. This gives us the ability to tweak the suggestion based on values in the future if we need to.

The following OOP hook goes into the same class as the formAlter() method we defined above. 

/**
 * Implements hook_theme_suggestions_HOOK_alter().
 */
#[Hook('theme_suggestions_input_alter')]
public function themeHookSuggestion(&$suggestions, array $variables) {
    $element = $variables['element'];
    // Mainly used to swap the search input to a button.
    if (isset($element['#attributes']['data-twig-suggestion'])) {
      $suggestions[] = 'input__' . $element['#type'] . '__' . $element['#attributes']['data-twig-suggestion'];
    }
}

This creates a suggestion for the template with the name of input--submit--search-results-submit.html.twig, which we can add to our theme.

The template for the input element can be added, which is basically the following code.

<button{{ attributes }}>
    <svg width="20" height=20 viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path d="M16.4999 16.5L12.0354 12.0355" stroke="#211E1E" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
        <path d="M8.5 13.5C11.2614 13.5 13.5 11.2614 13.5 8.5C13.5 5.73858 11.2614 3.5 8.5 3.5C5.73858 3.5 3.5 5.73858 3.5 8.5C3.5 11.2614 5.73858 13.5 8.5 13.5Z" stroke="#211E1E" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
    </svg>
</button>

Whilst the form will work correctly without the attributes in place, it is best to include them, especially since we added an aria-label for the search in the form alter hook and need that to be displayed.

With all this in place we now have a button instead of a input element for the search form. That button contains an inline SVG of a magnifying glass and can be further modified to add other text or elements.

I have thought about releasing this as a contributed module, but the search form detection and the resulting template tend to differ quite a bit from site to site.

Add new comment

The content of this field is kept private and will not be shown publicly.