Thoughts on find-in-page with custom accordion elements.
Tags: a11y, javascript, browsers, platform
Published: 2022-01-08 17:47:32

Today while scrolling the twitter timelines I stumble upon this interesting tweet by one of the google chrome engineer who is working on a pretty interesting problem on the internet which is being able to de-collapse accordions when user triggers a find-in-page action. 

{% tweet 1479525286211248128 %}

While as developers we sometimes miss these small details but after glancing over these problem you will realize how impactful these small things can be for better accessibility, discoverability & usability of a web app.

### The Problem

The general problem is easy to understand,  
Say you might want to search something on a page but you couldn't because the content which you are searching for is inside a collapsed accordion. 

I've built and seen many accordion components but each and every one of them lacked this feature, and there's a good reason for it which we will get into later.

### Research & implementation on the userland

After trying out few of the well known component libraries like Radix, ChakraUI. 
I decided "well whatever, let's just implement this. How hard could it be?"

Oh boi, I was in for a interesting ride of 50+ browser tabs searching for solution.

So to implement this on userland, we need to do few things

- Detect if user in find-in-page mode by detecting keypresses of CTRL+F
- Record the user's search keyword
- Match that keyword against all the accordion contents and de-collapse ones which matches the keyword.

Pretty simple right? Well NO! 

Just even detecting if user is in find-in-page mode or the user closed the search modal is tricky.

To properly detect the event, we have to record and save all the events which is happening in a `eventQueue`

See, when user presses CTRL+F first thing what happens is window gets out of focus or in other words the blur event is triggered, With this we can detect if find-in-page modal is open if `CTRL+F` event and `BLUR` event happened subsequently. 

Let's look at the code quickly: 

```ts

const usePageFind = () => {
  const [isFinding, setIsFinding] = React.useState(false);
  const [eventQueue, setEventQueue] = React.useState<string[]>([]);

  React.useEffect(() => {
    window.addEventListener("keydown", (e) => {
      // detect CTRL+F if it passes then push to events queue.
      if (e.key.toLowerCase() === "f" && e.ctrlKey) {
        setEventQueue((p) => [...p, "CTRL+F"]);
      }
    });
    window.addEventListener("blur", () => {
      // push blur event to queue
      setEventQueue((p) => [...p, "BLUR"]);
    });
    window.addEventListener("focus", (e) => {
      // push blur event to queue
      setEventQueue((p) => [...p, "FOCUS"]);
    });
  }, []);

  React.useEffect(() => {
    const openSlice = eventQueue.slice(-2);
    const closeSlice = eventQueue.slice(-3);
    // if eventQueue's last 2 elements are CTRL+F & BLUR then we know the find modal is open
    if (arrayCompare(openSlice, ["CTRL+F", "BLUR"])) {
      setIsFinding(true);
      console.log("Finding open");
    }
    // if eventQueue's last 3 elements are CTRL+F, BLUR & FOCUS then we know the find modal is closed
    // We are checking for FOCUS because if user closes the find modal the page will be refocused again.
    if (arrayCompare(closeSlice, ["CTRL+F", "BLUR", "FOCUS"])) {
      setEventQueue([]);
      setIsFinding(false);
      console.log("Finding closed");
    }
  }, [eventQueue]);

  return { isFinding };
};
```

And this is not even a perfect solution mind you. 

#### Retrieving search keyword 

But the real challenge here is detecting what user typed in the search field, because the window is blurred while user is searching we cannot hook into onKeyDown or any event handlers to know what user is typing. 

But there is a very very very hacky trick which we can use to detect this, which I found while researching about this topic. 

This article from Milan Laslop explained how the method works pretty well with implementation: 
https://www.milanlaslop.dev/post/2020-01-11-javascript-detecting-what-the-user-searches-on-the-page/

I just implemented this on our code and lets see what the final code looks like: 

> NOTE: THIS IS VERY VERY BUGGY, AND ONLY A POC. DO NOT TRY THIS AT HOME.
> Open this URL https://u8rtx.csb.app on a newtab for better experience

{% codesandbox u8rtx %}


## Thoughts

Now the above implementation which I created is not something I created to use in production nor it's a great solution, it's buggy, fragile, easy to break. 

I solely created the example to show you how nearly impossible it is to build this without proper platform APIs. 

### Better Solutions

Next we will talk about possible naive solutions to this problem which you can use today & what new features are coming in HTML spec to improve & solve this problem. 

#### Solution 1: De-collapse all the accordions on find-in-page trigger

A simple yet elegant solution would be to de-collapsing all the accordions in the page when we detect find-in-page event with our previously discussed usePageFind hook. 

{% codesandbox 04ehr %}

### Solution 2: Use the platform

As mentioned in the original tweet which `Joey Arhar` is working on chrome 97 now supports auto expanding of the <details /> elements which you can use. 

See live demo: https://auto-expanding-details.glitch.me/#target

#### With New APIs

Along with <details /> having built in support for this, since here we are talking about custom accordion elements we can also leverage new APIs which has been worked on namely: 

- `hidden=until-found` HTML attribute 
- and the `beforematch` event

These two together will enable us to build custom accordions with the same capability of text search in collapsed items.

Read [hidden content spec](https://github.com/WICG/display-locking/blob/main/explainers/hidden-content-explainer.md) to know how it works.


### Conclusion

And that concludes my today's research on this interesting problem, I hope you learned something along the way. 

It's amazing to see that browser are working on these type of features to improve the UX and the overall experience we have building websites which are generally more accessible. 
Hope to see more features like this to land. 

Without the new `hidden content` feature it's nearly impossible to build something like this. 

Generally I would like to see all modern component libraries adopt these new platform patters to improve their components. 

Links & glosarry 
- https://www.milanlaslop.dev/post/2020-01-11-javascript-detecting-what-the-user-searches-on-the-page/
- https://twitter.com/JosephArhar/status/1479525286211248128
- https://github.com/WICG/display-locking/blob/main/explainers/hidden-content-explainer.md