Welcome to FullStack. We use cookies to enable better features on our website. Cookies help us tailor content to your interests and locations and provide other benefits on the site. For more information, please see our Cookies Policy and Privacy Policy.
As users come to expect the same quality of UI from websites as in native applications, it’s a good idea to implement some native patterns that are common in native UIs. However, some patterns that work well for desktop applications don’t necessarily work for mobile, and vice versa.
This can pose a challenge when building responsive web applications that have to take into account multiple layouts and screen widths. Today, we’ll implement a filter component that adapts not only its layout but also its behavior according to the device’s screen size to provide a UI that feels comfortable on both mobile and desktop devices.
What we’re building
Let's start with a mockup of the component to implement. The idea is to provide users with a button that, when active, will display a dropdown on desktop and a fullscreen modal on mobile:
In order to achieve this, we’ll implement a single component that will, depending on the screen, display one component or the other.
Implementing the button
The first step will be implementing a button that will hold the state of whether the filter is open or not. We’ll also apply some basic styling so that our button looks good:
Our button looks pretty good, but it doesn’t do much. Let’s fix that by showing a drop-down whenever the isOpen state is set to true and adding the button in charge of applying the filter:
We need to make sure that the dropdown stays hidden on smaller screens. There’s a couple of ways we could do this, but to keep it simple we’ll use a media query and hide the dropdown on smaller screens:
There’s one last piece left in our dropdown implementation. Whenever a user clicks outside of the floating dropdown, they’d expect the modal to close. In order to implement that, we’ll have to enhance our component with a couple of Refs and the useEffect hook:
useEffect(() => {
const handleClickOutside = event => {
const isDropdownClick =
dropdownRef.current && dropdownRef.current.contains(event.target);
const isButtonClick =
buttonRef.current && buttonRef.current.contains(event.target);
if (isDropdownClick || isButtonClick) {
// If the ref is not defined or the user clicked on the menu, we don’t do anything.return;
}
// Otherwise we close the menu. setIsOpen(false);
};
document.addEventListener("mousedown", handleClickOutside); // handle desktopsdocument.addEventListener("touchstart", handleClickOutside); // handle touch devices// Event cleanupreturn() => {
document.removeEventListener("mousedown", handleClickOutside); // handle desktopsdocument.removeEventListener("touchstart", handleClickOutside); // handle touch devices };
}, [dropdownRef, buttonRef]);
This will prevent the dropdown from unexpectedly closing while the user is interacting with the filter.
Protip: You could further refactor this useEffect call into a custom useClickOutside hook to avoid cluttering your component.
Let’s have a look at how our component is going so far:
So far so good! Now let’s continue with the implementation for mobile devices.
Implementing the modal for mobile
Modals seem like a very simple concept on paper: just cover the whole screen with a view on top of everything else. However, they can be quite tricky to get right especially if you want to provide an accessible modal. Therefore we’ll be leveraging Reach UI to help us manage focus states, semantic elements and other complexities associated with modal components.
Let’s start by creating our own <filtermodal ></filtermodal > component, which we can later bring into the <filter></filter> component:
Now we have a modal component that will only appear on small screens:
Great, we’re ready to pull this modal into our Filter component. Notice that the modal accepts a Ref. Just like with the dropdown, we’ll need to use this to prevent the modal from closing unexpectedly when the user interacts with it. We’ll also need to provide some props to handle the dismiss and apply actions:
And with that, we’ve implemented all the behavior we need for our responsive filter to handle small and large screens. But before we move on to implement features with our new component, let’s clean it up a little and make it more general purpose.
Making it reusable
One of the best things about React is that we can extract UI blocks into reusable components. Let’s enable our Filter component to receive arbitrary content from any screen. We’ll use React’s composition model to allow our Filter to accept a children prop. This will let the parent element control what kind of content goes inside our filter component.
Let's take a look at our completed Filter component:
Now let's implement an actual user input to see how this all fits together. We’ll implement a multiple select filter where a user can select different technologies:
We can see how our Filter component displays a modal with the checkboxes on a mobile screen:
And how it displays a dropdown on desktop:
Closing thoughts
As a Senior Software Engineer working with other talented UI designers at FullStack Labs, a React consultancy with experience in creating complex user interfaces, I hope this article gave you a clear example of how to use composition in React to build reusable components.
What is the purpose of the responsive filter component?
The filter component improves usability across devices by adapting its layout. On desktop, it shows a dropdown for selecting filters, while on mobile, it displays a full-screen modal. This ensures a consistent and user-friendly experience.
How does the component handle different layouts for mobile and desktop?
The component relies on conditional rendering and CSS media queries to display the right interface. Desktop users see a compact dropdown, while mobile users get a larger, touch-friendly modal for easier interaction.
How does the filter close when clicking outside the dropdown or modal?
By using React’s useRef and useEffect hooks, the component detects clicks outside the dropdown or modal. If a user interacts anywhere else on the page, the filter automatically closes, improving usability and preventing unwanted selections.
Why is Reach UI used for the modal?
Reach UI provides built-in accessibility support, handling focus management, ARIA roles, and keyboard navigation. It simplifies creating an accessible modal that works smoothly on both desktop and mobile devices without additional configuration.
Can the filter component be reused across multiple pages?
Yes. The component is designed to be reusable by leveraging React’s composition model. Developers can pass custom content as children, allowing the same component to support various filtering needs across different pages or features.
AI is changing software development.
The Engineer's AI-Enabled Development Handbook is your guide to incorporating AI into development processes for smoother, faster, and smarter development.
Enjoyed the article? Get new content delivered to your inbox.
Subscribe below and stay updated with the latest developer guides and industry insights.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.