Image of different devices (mobile, tablet and desktop)

Responsive design is almost a mandatory requirement for every web project you have to handle these days and most of us use this mobile-first approach where we begin designing for small devices (because most of your visits will come from them) and then adapt that same design for bigger screen resolutions.

When you are implementing these designs you deal with all those needs in terms of html markup and CSS so you are able to assemble a single source file to support the view in the different resolutions.
However, there are times where you may require a quite different design for different devices, such as mobile and desktop.
When I get to this point, I prefer to have separate source files for these alternative views so I can:

  • Focus on the subtleties of each design
  • Focus the CSS styles for each one with their requirements
  • Make each one easier to reason about and mantain
  • Render only what is needed in each situation

Last week I was following this fantastic tutorial by Adam Wathan on TailwindCSS (he’s the creator) and got to this situation where one of the components that has to be built have different requirements for small and big screens.

We had this profile button you can click on (in desktop version) to display different links regarding user actions. Also it had some olverlay behavior.

Here is the button with no interaction by the user:

Profile button (closed)

And here it is when the user clicks on it:

Profile button (opened)

Now let’s see the design for small resolution screens.
In this case, the profile button is hidden by default and it’s only shown when the user opens the links menu:

Navigation menu opened in mobile

You can for sure have just one component that adapts the HTML markup, styles and behavior for both contexts, but this is just one of those situations where I prefer to have separate ones.

So, let’s jump to the (React) code…

This is the component for big screens:

As you can see, there is the icon button, an overlay and the links.
Links and overlay are hidden by default and they are displayed only when the user click on the icon.
There is also code for closing the menu when pressing the Escape key.

Now let’s move to the small resolutions version of this component:

This is a smaller component as it doesn’t need the overlay and links behavior.
Also, we are rendering the user name alongside the user icon.

Not only do I find easier to mantain these two components, but I am also able to render only what it’s required.
I mean, in traditional responsive implementation we normally hide those elements that are not mean to be shown in one specific resolution, but what if we do not render them at all? (little performance improvement).

So, the key for using this pattern is that consumers of this component do not know that there are different versions of it.
Here you can see how both these components are used in the context of the header of this fictional website.

We’re just adding this AccountDropdown (maybe not the best name, I know…) in the nav element.

So, if we have two components, how do we export just one interface to the consumers?

We have this interface/proxy component which is the one that exports the specific implementation based on the current user’s screen resolution.
This is what I’m talking about:

This is the component we’re importing from the website header.
As you can see, the magic is happening at another utility component called ResponsiveComponent.
We’re feeding this component with loaders for alternative AccountDropdown based on breakpoints.
We’re passing loaders instead of the components so we can lazy-load them and save us from downloading the version we’re not using on the browser.

Ok, let’s move to the ResponsiveComponent to check its implementation:

As I was playing with TailwindCSS I borrowed their breakpoints.
The gist here is that you can provide different versions for every screen resolution you’re dealing with in your project (regarding your breakpoints).
You don’t need to provide all of them. In this case I’m just passing a version for the smallest resolution and another one for the rest.
ResponsiveComponent will use the smallest version avialable, based on the screen resolution and the configured breakpoints.

Note we’re using Suspense here to lazy-load this component and we can provide a Loading component to render while the actual one is being loaded.

Also notice how we’re using this selectResponsiveComponent function from a custom hook imported from a ResponsiveProvider to decide which specific component version of AccountDropdown we must use.

The thing is that we need somewhere to calculate and hold the state for the current screen resolution so we can later use that information when it’s time to pick an alternative resolution rendering component.
Also, we need to consider the screen resolution changes to update that state.

So, here is the code for that ResponsiveProvider where we implement a React context to manage all this stuff and also export the custom hook with required function utilities:

Here we’re first calculating the current breakpoint based on user browser resolution and then keeping it up to date if the browser window’s size changes.
We select the smallest breakpoint available.

Summary

I know we should aim for single component responsive design implementations but, at least for me, there are situations where having different components for each version is more convenient.

Here I explained the pattern I prefer using in these kind of scenarios where I try to hide the rest of the application if these components exist at all and be flexible enough to deal with the different breakpoints I’m using in the project.

You can find the whole code in this repo where you might be able to better understand how all the pieces work together.

I was thinking in publishing the ResponsiveProvider and ResponsiveComponent as a NPM package for others to use but I’m not sure if there are many people out there that share this responsive rendering perspective with me.

Let me know your thoughts on this one.

Thanks!