diff --git a/app/assets/stylesheets/_autocomplete.scss b/app/assets/stylesheets/_autocomplete.scss
new file mode 100644
index 000000000..585ba109c
--- /dev/null
+++ b/app/assets/stylesheets/_autocomplete.scss
@@ -0,0 +1,44 @@
+// Ensure the autocomplete uses the correct typeface
+.autocomplete__wrapper {
+ font-family: $govuk-font-family;
+}
+
+.autocomplete__input {
+ font-family: inherit;
+}
+
+// Style the autocomplete if there’s an error
+.nhsuk-form-group--error {
+ .autocomplete__input {
+ border-color: $govuk-error-colour;
+ }
+
+ .autocomplete__input--focused {
+ border-color: $govuk-input-border-colour;
+ }
+}
+
+.autocomplete__dropdown-arrow-down {
+ // Ensure dropdown arrow can be clicked
+ // https://github.com/alphagov/accessible-autocomplete/issues/202
+ pointer-events: none;
+ // Ensure dropdown arrow can be seen
+ z-index: 0;
+}
+
+.autocomplete__input {
+ background-color: $color_nhsuk-white;
+}
+
+.autocomplete__option {
+ margin-bottom: 0;
+}
+
+.autocomplete__option-hint {
+ color: $nhsuk-secondary-text-color;
+
+ .autocomplete__option:hover &,
+ .autocomplete__option:focus & {
+ color: $color_nhsuk-grey-5;
+ }
+}
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 8e13efa63..579107a62 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -33,6 +33,7 @@ $color_app-dark-orange: darken(
// Application components
@import "action_list";
+@import "autocomplete";
@import "button";
@import "button-group";
@import "card";
diff --git a/app/javascript/controllers/autocomplete_controller.js b/app/javascript/controllers/autocomplete_controller.js
new file mode 100644
index 000000000..5ddf32d70
--- /dev/null
+++ b/app/javascript/controllers/autocomplete_controller.js
@@ -0,0 +1,60 @@
+import { Controller } from "@hotwired/stimulus";
+import accessibleAutocomplete from "accessible-autocomplete";
+
+const enhanceOption = (option) => {
+ return {
+ name: option.label,
+ append: option.getAttribute("data-append"),
+ hint: option.getAttribute("data-hint"),
+ };
+};
+
+const suggestion = (value, options) => {
+ const option = options.find(({ name }) => name === value);
+ if (option) {
+ const html = option.append ? `${value} – ${option.append}` : value;
+ return option.hint
+ ? `${html}
${option.hint}`
+ : html;
+ } else {
+ return "No results found";
+ }
+};
+
+const autocomplete = ($module) => {
+ if (!$module) {
+ return;
+ }
+
+ const params = $module.dataset;
+
+ const selectOptions = Array.from($module.options);
+ const options = selectOptions.map((option) => enhanceOption(option));
+
+ accessibleAutocomplete.enhanceSelectElement({
+ autoselect: params.autoselect === "true",
+ defaultValue: params.defaultValue || "",
+ displayMenu: params.displayMenu,
+ minLength: params.minLength ? parseInt(params.minLength) : 0,
+ selectElement: $module,
+ showAllValues: params.showAllValues === "true",
+ showNoOptionsFound: params.showNoOptionsFound === "true",
+ templates: {
+ suggestion: (value) => suggestion(value, options),
+ },
+ onConfirm: (value) => {
+ const selectedOption = [].filter.call(
+ selectOptions,
+ (option) => (option.textContent || option.innerText) === value,
+ )[0];
+ if (selectedOption) selectedOption.selected = true;
+ },
+ });
+};
+
+// Connects to data-module="autocomplete"
+export default class extends Controller {
+ connect() {
+ autocomplete(this.element);
+ }
+}
diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js
index b43fa97c8..b3b849730 100644
--- a/app/javascript/controllers/index.js
+++ b/app/javascript/controllers/index.js
@@ -4,6 +4,9 @@
import { application } from "./application";
+import AutocompleteController from "./autocomplete_controller";
+application.register("autocomplete", AutocompleteController);
+
import AutosubmitController from "./autosubmit_controller";
application.register("autosubmit", AutosubmitController);
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 0b283b93a..16818ec74 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -6,6 +6,7 @@