# Building a Command Palette with Remix and Tailwind UI

Recently, the Tailwind Labs team published a new Combobox component on Headless UI and used it together with Dialog to create a Command Palette component in Tailwind UI.

I saw how simple it was and decided to add one for my work app. I'm not going to give you the code of the UI in this case because it's a paid component but let's see how to implement most of the other things with Remix.

## The API Endpoint

We will build a command palette to search people on the DB and show them. Let's start with a resource route we'll use as an API endpoint to get the data.

```ts
import { LoaderFunction, json } from "remix";
import type { User } from "~/types";

export type LoaderData = {
  users: User[];
};

export let loader: LoaderFunction = async ({ request }) => {
  let url = new URL(request.url);
  let term = url.searchParams.get("term");

  // this function should query the DB or fetch an API to get the users
  let users = await getUsersByName(term);

  return json({ users });
};
```

We have a simple endpoint to give us the data we need.

## The Component

Now let's build the UI. We will imagine a CommandPaletteUI component that receives some props and renders the UI from the [simple command palette example on Tailwind UI](https://tailwindui.com/components/application-ui/navigation/command-palettes#component-3b1e5d9bec70736c7d1afca2585ec39e).

Let's build the component with some mocked data:

```tsx
// fake package
import { CommandPaletteUI } from "@tailwindui/react";
import { useState } from "react";

let people = [
  { id: 1, name: "Michael Jackson", url: "#" },
  { id: 2, name: "Ryan Florence", url: "#" },
  { id: 3, name: "Kent C. Dodds", url: "#" },
];

export function CommandPalette() {
  let [query, setQuery] = useState("");
  let [value, setValue] = useState(() => people[0]);

  let filteredPeople = people.filter((person) =>
    person.name.toLowerCase().includes(query.toLowerCase())
  );

  return (
    <CommandPaletteUI
      data={filteredPeople}
      value={value}
      onSelect={setValue}
      query={query}
      onQueryChange={setQuery}
      isLoading={false}
    />
  );
}
```

Now, let's start fetching the data from the API endpoint and updating the UI instead of using the hardcoded list of people.

To do the fetch, we can use the [useFetcher](https://remix.run/docs/en/v1/api/remix#usefetcher) hook, which is perfect for this use cases.

```tsx
// fake package
import { CommandPaletteUI } from "@tailwindui/react";
import { useState, useEffect } from "react";
import { useFetcher } from "remix";
// because of `import type` this will let us import the LoaderData without
// importing the rest of the module code
import type { LoaderData } from "~/routes/api.command-palette";

export function CommandPalette() {
  let [query, setQuery] = useState("");
  let [value, setValue] = useState(() => people[0]);

  let { data, load, state } = useFetcher<LoaderData>();
  let people = data?.users ?? []; // initially data is undefined

  useEffect(
    function getInitialData() {
      load("/api/command-palette");
    },
    [load]
  );

  useEffect(
    function getFilteredPeople() {
      load(`/api/command-palette?term=${query}`);
    },
    [load, query]
  );

  return (
    <CommandPaletteUI
      data={people}
      value={value}
      onSelect={setValue}
      query={query}
      onQueryChange={setQuery}
      isLoading={state === "loading"}
    />
  );
}
```

And that's it. Remix makes data fetching so easy that the change from mocked data to actual data is small and straightforward without many changes.