Showing posts with label mobile. Show all posts
Showing posts with label mobile. Show all posts

How to Do Mobile Testing Right

Mike's Notes

This is an excellent article by Luca Rossi from the Substack Refactoring on testing applications for mobile devices.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library > Subscriptions > Refactoring
  • Home > Handbook > 

Last Updated

15/05/2025

How to Do Mobile Testing Right

By: Luca Rossi
Refactoring: 14/05/2025

A thorough guide that includes practical playbooks for companies at every stage.

Mobile apps are the primary interface through which hundreds of millions of people interact with services daily.

Smartphones have been a thing for 15+ years, during which they have arguably changed… not so much, especially in recent years. For this reason, it may be reasonable to expect a flawless experience at every tap.

Instead, the reality is different and painful — especially for mobile engineering teams: an ever-expanding universe of devices, hardware, OS versions, screen resolutions, and capabilities, that software must navigate correctly.

Welcome to hell mobile fragmentation.

In my previous life as a startup CTO, I ran a travel web app that had native counterparts on iOS and Android, and I swear that mobile testing and QA was one of the things that kept me up at night. It is incredibly hard to do it right, yet supremely important, having a direct impact on user satisfaction, churn, and ultimately, the bottom line.

So today we are publishing a full guide on fragmentation testing which provides a comprehensive but pragmatic approach to the problem, by covering principles, strategies, and tools.

We are doing so by drawing from my own learnings, from the ones of people in the Refactoring community, and by bringing in the invaluable experience of Nakul Aggarwal, CTO and co-founder of BrowserStack.

BrowserStack is a cornerstone of how thousands of teams — including my former own — engage with real-device testing at scale, and Nakul is one of the world’s most knowledgeable people in this space.

So, as you will see, success in mobile testing is about making smart choices, and focusing your efforts where they yield the greatest return. We'll explore how to define "good enough" in a world of infinite variables, and how to build a testing approach that supports, rather than hinders, your engineering velocity.

Here is the agenda:

  • What is mobile fragmentation? — defining the beast and its many heads.
  • Cost of fragmentation — the real-world business consequences.
  • Fragmentation testing playbook — how to build your full testing process, from strategy down to tactics.
  • Testing strategy vs product maturity — how your playbook should evolve over time.
  • Navigating the trade-offs — balancing cost, speed, coverage, and developer experience.
  • The Future is Still Fragmented — trends, the role of AI, and some closing notes

Let's dive in!

What is Mobile Fragmentation?

We've thrown the term "mobile fragmentation" around, but what do we mean by that? Spoiler: it’s not something that happens to your phone screen after a drop.

At its core, mobile fragmentation is the sheer diversity of hardware and software across the millions of devices your application might encounter in the wild.

Such diversity is also multi-dimensional:

  • Device manufacturers (the who) — from the big players like Apple and Samsung, to the long tail of Xiaomi, Oppo, OnePlus, and the countless regional champions. Each comes with its own hardware quirks, custom Android skins, and unique interpretations of how Android should behave.
  • Operating systems & versions (the what & when) — you need to account for multiple major versions active concurrently. Update rollouts lead to notorious lags, and some devices never get updated beyond a certain point. This is true for both iOS and Android, with Android being typically much worse.
  • Screen sizes & resolutions (the where) — today’s smartphones range from compact screens, to phablets, foldables, and tablets: the range is vast. Other than physical size, you may need to account for pixel density, aspect ratios, and newer features like dynamic refresh rates or screen cutouts (notches, punch-holes), that can all wreak havoc on your UI if not handled gracefully.
  • Hardware differences (the how) — beneath the glass, there's even more: processors, memory constraints, GPUs, and sensors, which may or may not make a difference in how your app behaves.

Trying to account for every permutation is impossible. Understanding these dimensions, however, is the first step to building a smarter strategy.

The Cost of Fragmentation

One of the things I always try to do at Refactoring is to think from first principles, so the first question here is: what if you just ignore this? Seriously, let’s not take anything for granted.

How bad is this, for real?

Unfortunately, when fragmentation is managed poorly, it bites hard — on your users, your team, and the business:

  • Poor UX — the most immediate impact. Users encounter crashes, freezes, baffling UI glitches, or sluggish performance that makes the app feel broken. Frustrated users are 1) unlikely to give you a second chance, and 2) they often head straight to…
  • Bad reviews — users are quick to voice their displeasure, and negative App Store reviews are incredibly damaging—it doesn’t matter if they are about a small set of devices. A flood of "unusable on Android 12" reviews will torpedo your app's rating, affecting everyone.
  • Churn — if an existing user has a persistent issue on their device after an update, or a new user has a terrible first experience, they're likely to abandon your app. Acquiring users is expensive: losing them due to preventable issues is a painful, self-inflicted wound.
  • Support costs — your support team gets swamped with tickets and complaints related to device-specific bugs. Diagnosing these can be a nightmare, requiring detailed information about device models, OS versions, and steps to reproduce that users often struggle to provide.
  • Slower dev velocity (ironically) — if you are trying to move faster by avoiding thorough testing, think again. Fragmentation bugs in production can lead to constant firefighting and a reactive development cycle. This drains morale and pulls your team away from feature development.

Investing in a good testing strategy isn't just about "quality assurance" in an abstract sense: it's about protecting your revenue, your reputation, and your team's ability to move fast.

So how do you do that? Enter the playbook

Fragmentation Testing Playbook

We have established the why — now for the how. This section should work as your tactical playbook: the core strategies and tools you'll use to construct a robust, pragmatic mobile fragmentation testing process.

This playbook focuses on four key pillars:

  • Device matrix — your clear, data-driven plan of which devices and OS versions matter the most to your users.
  • Testing mix — a balanced portfolio of emulators, real devices, and cloud solutions to maximize coverage and efficiency.
  • Foundational quality — strong architecture, base testing, and monitoring to significantly reduce the number of bugs that reach device-specific testing.
  • Automation strategy — manual testing doesn't scale, and smart automation is crucial for maintaining velocity and reducing toil for your team.

Let's break these down

1) Build your device matrix

Your Device Matrix is the single most important artifact guiding your testing. It’s a curated inventory of devices and OS versions, tiered by importance, against which you validate your app. Here is how you build one:

1.1) Know your actual users (be data driven)

First of all, be data driven. Prioritize based on which devices, OS versions, and even geographical regions are most prevalent among your user base.

Action — Dive into your analytics. Understand your specific device landscape. What are the top 10, 20, 50 devices your active users are on? What OS versions dominate? This data is the bedrock of your device matrix.

1.2) Prioritize (risk assessment)

Not all devices or features are created equal in terms of risk.

Risk, in this context, is a function of likelihood (how many users on this device/OS?) and impact (how critical is this feature? What happens if it breaks?).

Action — Focus your most intensive testing on high-traffic user flows running on the most popular devices/OS versions within your user base. A bug in your checkout process on your top 5 Android devices is infinitely more critical than a minor UI glitch on an obscure device with little market share among your users.

1.3) Define tiers (risk acceptance)

Since you can't test everything equally, you need to explicitly define levels of risk you're willing to accept for different device segments. This formalizes your prioritization.

Action — Create device/OS tiers. For example:

    • Tier 1 (Critical) — your most popular devices/OS versions (e.g. top 80% of your user base). Bugs here are unacceptable. These devices get the full suite of manual and automated tests for all critical and important features.
    • Tier 2 (Important) — the next significant chunk of devices. Minor cosmetic issues might be tolerable, but core functionality must work. These might get critical path automation and focused manual testing.
    • Tier 3 (Supported/Best Effort) — older or less common devices. You aim for basic functionality, but known issues might be documented and not block a release if non-critical. Testing might be limited to smoke tests or exploratory testing if time permits.

1.4) Keep it alive

Finally, create a process in which you review and update the matrix on a periodic basis (e.g. quarterly), as your user base and the market will inevitably evolve. Your matrix is only useful as long as it is up to date.

2) Create your testing mix

No single testing method conquers fragmentation, but a balanced portfolio might do. Here are the most common approaches:

  • Emulators & Simulators — emulators are the first line of defense for developers. They are fast, free, scalable for basic layout and functional bug checks during development. However, they can't perfectly replicate real hardware performance, sensor behavior, or OEM-specific OS mods.
  • Real devices (In-house lab) — they provide the highest fidelity for performance, hardware interactions, and manufacturer quirks… but they can be expensive and logistically challenging to maintain.
  • Cloud device farms — the scalable solution for broad real-device testing (manual and automated) without owning hardware. Platforms like BrowserStack give you on-demand access to thousands of physical devices/OS versions globally, and allow precise matrix mirroring and massive test parallelization.

3) Establish foundational quality

Strong underlying code quality and good production significantly eases the load on device-specific testing. Your goal should be to minimize the number of issues that get to device-level, by intercepting them earlier:

  • Strong typing & static analysis — strongly typed languages like TypeScript (React Native), Kotlin, and Swift help you catch a lot of errors before runtime. Employ linters for further analysis.
  • Robust unit & integration tests — ensure core logic, utilities, and API integrations are thoroughly covered. Unit and integration tests are fast and cheap to run, especially compared to E2E tests.
  • Architect for testability — design choices matter. Keep the mobile app light by pushing as much business logic as possible into the backend layer, where it is easier to test. If you are using a universal framework like React Native or Flutter, restrain as much as possible from writing platform-specific code.
  • Intensive logging & production monitoring — your safety net. Implement good monitoring with tools like Firebase Crashlytics or Sentry, to catch issues that slip through as early as possible.

4) Automation strategy

Manual testing across a large matrix is unsustainable, but implementing automation across the board can be equally hard. Do smart automation choices for streamlining key areas while maintaining velocity:

  • Focus your automation — don't try to automate everything. Prioritize critical user flows ("must not break" scenarios) on your Tier 1 devices. Use well-established frameworks (Appium, Espresso, XCUITest).
  • Parallelize with cloud platforms — running suites sequentially is a bottleneck. Cloud platforms enable massive parallel execution across hundreds of configurations, providing fast feedback in CI/CD.
  • Incorporate visual regression testing — for UI-heavy apps, these tools automatically detect visual changes across devices, catching layout bugs functional tests miss.
  • Reduce toil & boost DevEx — automation's goal is to free your team from repetitive manual checks, leading to faster, more reliable feedback and higher developer confidence.

Testing Strategy vs Product Maturity

The principles we covered should somewhat work for everyone, but the truth is your specific approach to fragmentation should also change over time, and evolve alongside your product and team journey.

Obviously, applying an enterprise level of testing rigor to a pre-PMF product is a waste of resources, just as neglecting deeper testing once you have scale is a recipe for disaster.

So let's try mapping your fragmentation strategy to the typical QA / Product Journey stages we have discussed other times in the past.

Your fragmentation strategy should evolve alongside your product

There is a lot of nuance and “your mileage may vary” here, but let’s sketch a basic cheatsheet:

1) Zero-to-One

  • Focus — Speed, iteration, and validating core hypotheses.
  • Fragmentation Approach — Minimal and highly pragmatic.
    • Device "Matrix" — Likely just the founders' phones, maybe a couple of common emulators/simulators for basic layout checks. A formal matrix is overkill.
    • Testing — Primarily manual, "happy path" testing on these few devices. Does the core loop work? Can users sign up and perform the one key action?
    • Automation — Probably none, or at most, some very basic UI smoke tests if the team has a strong existing preference.
    • Risk Tolerance — Very high. Bugs are expected. The bigger risk is building the wrong product, not having a perfectly polished app on every obscure device.
  • Takeaway — Don't let fragmentation concerns prematurely slow you down. Focus on finding PMF.

2) Finding PMF / Early Growth

  • Focus — Stabilizing core features, growing the user base, and starting to understand user segments.
  • Fragmentation Approach — Begin to formalize, driven by initial user data.
    • Device Matrix — Start tracking user analytics (even basic ones from e.g. Firebase or your app store consoles). Identify your top 5-10 devices/OS versions. This forms your rudimentary, evolving matrix.
    • Testing — Still heavily manual, but more structured. Test critical user flows on your identified key devices. Introduce more thorough exploratory testing.
    • Automation — Consider introducing UI automation for 1-2 absolute critical paths (e.g., signup, core purchase flow) if you have the expertise. Keep it lean.
    • Tools — This is where you might start dipping your toes into cloud device services for occasional checks on devices you don't own, especially if user feedback points to issues on specific models.
    • Risk Tolerance — Medium. Core functionality on popular devices needs to be solid. You can still live with some rough edges on less common configurations.
  • Takeaway — Use early data to guide a Minimum Viable Testing process for fragmentation.

3) Scaling / Established Product

  • Focus — Reliability, performance at scale, expanding feature sets, and protecting brand reputation.
  • Fragmentation Approach — Strategic, data-driven, and increasingly automated.
    • Device Matrix — A well-defined, multi-tiered matrix (as discussed in "First Principles") is essential, constantly updated with fresh user analytics and market data.
    • Testing — A more sophisticated mix:
      • Manual — Focused exploratory testing, usability checks, and testing new features on key devices.
      • Automated — Significant investment in UI automation for regression testing across Tier 1 and critical Tier 2 devices, running in CI/CD.
    • Tools — Heavy reliance on cloud device farms (like BrowserStack) for comprehensive automated and manual testing coverage across the matrix. You might also maintain a small, curated in-house lab for frequently used dev/QA devices. Also this balance can shift over time.
    • Performance Monitoring — Actively monitor performance and stability across different device segments in production.
    • Risk Tolerance — Low to Very Low for Tier 1 devices and critical functionality. Higher for Tier 3.
  • Takeaway — Your fragmentation strategy is now a core part of your quality engineering process, deeply integrated and data-informed.

The Future is Still Fragmented

So, will fragmentation ever end?

Probably not, at least not anytime soon. While there are glimmers of hope (Google's Project Mainline, for instance), the fundamental drivers of diversity remain.

Hardware innovation in smartphones might be questionable today, but new form factors emerge all the time (foldables are already here, wearables are well established, and AR/VR may be on the horizon), and OS customizations persist.

What about AI?

There's certainly potential for AI to assist with this. AI may write test cases, better simulate E2E flows, and even predict high-risk device/OS combinations based on code changes.

However, for the core challenge of executing tests across diverse hardware remains, and AI is not a silver bullet.

The reality is that the right mindset and an intentional strategy (data-driven device matrix, smart testing mix, strong foundational quality, targeted automation) remain your most crucial assets for navigating the mobile landscape.

The landscape will shift, but the principles of smart, risk-based testing will endure.

Bottom line

And that’s it for today! Remember that navigating the fragmented world of mobile devices is a marathon, not a sprint. Here are some takeaways from today’s guide:

  • Let data drive your device matrix — your actual user analytics are the most reliable guide for deciding which devices and OS versions deserve your primary testing focus. Don't guess; know.
  • Embrace tiered, risk-based testing — not all devices or bugs are created equal. Prioritize ruthlessly, focusing maximum effort on high-impact areas and accepting calculated risks elsewhere.
  • Blend your testing mix wisely — combine emulators (for speed), a curated in-house lab (for frequent access/fidelity), and cloud device farms like BrowserStack (for breadth, scale, and specialized needs).
  • Build on foundational quality — strong typing, linting, robust unit/integration tests, and good architectural choices significantly reduce the burden on expensive end-to-end device testing.
  • Automate strategically, not exhaustively — focus UI automation on stable, critical user flows on your most important devices to reduce toil and get fast feedback, leveraging parallel execution in the cloud.
  • Evolve your strategy with maturity — the right level of testing rigor changes as your product grows from pre-PMF to scale. Continuously adapt your approach.

Final note: I want to thank Nakul for joining in on this. I am a fan of what BrowserStack is building for mobile testing and AI-powered workflows. You can learn more below

Mobile-First CSS: Is It Time for a Rethink?

Mike's Notes

I'm thinking ahead here. The current priority is first building Pipi websites for desktops and laptops because Pipi's job is building work tools. Later, mobile apps will need to be accommodated.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library >
  • Home > Handbook > 

Last Updated

17/05/2025

Mobile-First CSS: Is It Time for a Rethink?

By: Patrick Clancey
A List Apart: 08/06/2022

The mobile-first design methodology is great—it focuses on what really matters to the user, it’s well-practiced, and it’s been a common design pattern for years. So developing your CSS mobile-first should also be great, too…right? 

Well, not necessarily. Classic mobile-first CSS development is based on the principle of overwriting style declarations: you begin your CSS with default style declarations, and overwrite and/or add new styles as you add breakpoints with min-width media queries for larger viewports (for a good overview see “What is Mobile First CSS and Why Does It Rock?”). But all those exceptions create complexity and inefficiency, which in turn can lead to an increased testing effort and a code base that’s harder to maintain. Admit it—how many of us willingly want that?

On your own projects, mobile-first CSS may yet be the best tool for the job, but first you need to evaluate just how appropriate it is in light of the visual design and user interactions you’re working on. To help you get started, here’s how I go about tackling the factors you need to watch for, and I’ll discuss some alternate solutions if mobile-first doesn’t seem to suit your project.

Advantages of mobile-first

Some of the things to like with mobile-first CSS development—and why it’s been the de facto development methodology for so long—make a lot of sense:

  • Development hierarchy. One thing you undoubtedly get from mobile-first is a nice development hierarchy—you just focus on the mobile view and get developing. 
  • Tried and tested. It’s a tried and tested methodology that’s worked for years for a reason: it solves a problem really well.
  • Prioritizes the mobile view. The mobile view is the simplest and arguably the most important, as it encompasses all the key user journeys, and often accounts for a higher proportion of user visits (depending on the project). 
  • Prevents desktop-centric development. As development is done using desktop computers, it can be tempting to initially focus on the desktop view. But thinking about mobile from the start prevents us from getting stuck later on; no one wants to spend their time retrofitting a desktop-centric site to work on mobile devices!

Disadvantages of mobile-first

Setting style declarations and then overwriting them at higher breakpoints can lead to undesirable ramifications:
  • More complexity. The farther up the breakpoint hierarchy you go, the more unnecessary code you inherit from lower breakpoints. 
  • Higher CSS specificity. Styles that have been reverted to their browser default value in a class name declaration now have a higher specificity. This can be a headache on large projects when you want to keep the CSS selectors as simple as possible.
  • Requires more regression testing. Changes to the CSS at a lower view (like adding a new style) requires all higher breakpoints to be regression tested.
  • The browser can’t prioritize CSS downloads. At wider breakpoints, classic mobile-first min-width media queries don’t leverage the browser’s capability to download CSS files in priority order.

The problem of property value overrides

There is nothing inherently wrong with overwriting values; CSS was designed to do just that. Still, inheriting incorrect values is unhelpful and can be burdensome and inefficient. It can also lead to increased style specificity when you have to overwrite styles to reset them back to their defaults, something that may cause issues later on, especially if you are using a combination of bespoke CSS and utility classes. We won’t be able to use a utility class for a style that has been reset with a higher specificity.

With this in mind, I’m developing CSS with a focus on the default values much more these days. Since there’s no specific order, and no chains of specific values to keep track of, this frees me to develop breakpoints simultaneously. I concentrate on finding common styles and isolating the specific exceptions in closed media query ranges (that is, any range with a max-width set). 

This approach opens up some opportunities, as you can look at each breakpoint as a clean slate. If a component’s layout looks like it should be based on Flexbox at all breakpoints, it’s fine and can be coded in the default style sheet. But if it looks like Grid would be much better for large screens and Flexbox for mobile, these can both be done entirely independently when the CSS is put into closed media query ranges. Also, developing simultaneously requires you to have a good understanding of any given component in all breakpoints up front. This can help surface issues in the design earlier in the development process. We don’t want to get stuck down a rabbit hole building a complex component for mobile, and then get the designs for desktop and find they are equally complex and incompatible with the HTML we created for the mobile view! 

Though this approach isn’t going to suit everyone, I encourage you to give it a try. There are plenty of tools out there to help with concurrent development, such as Responsively App, Blisk, and many others. 

Having said that, I don’t feel the order itself is particularly relevant. If you are comfortable with focusing on the mobile view, have a good understanding of the requirements for other breakpoints, and prefer to work on one device at a time, then by all means stick with the classic development order. The important thing is to identify common styles and exceptions so you can put them in the relevant stylesheet—a sort of manual tree-shaking process! Personally, I find this a little easier when working on a component across breakpoints, but that’s by no means a requirement.

Closed media query ranges in practice 

In classic mobile-first CSS we overwrite the styles, but we can avoid this by using media query ranges. To illustrate the difference (I’m using SCSS for brevity), let’s assume there are three visual designs: 

  • smaller than 768
  • from 768 to below 1024
  • 1024 and anything larger 

Take a simple example where a block-level element has a default padding of “20px,” which is overwritten at tablet to be “40px” and set back to “20px” on desktop.

Classic min-width mobile-first

.my-block {
  padding: 20px;
  @media (min-width: 768px) {
    padding: 40px;
  }
  @media (min-width: 1024px) {
    padding: 20px;
  }
}

Closed media query range

.my-block {
  padding: 20px;
  @media (min-width: 768px) and (max-width: 1023.98px) {
    padding: 40px;
  }
}
The subtle difference is that the mobile-first example sets the default padding to “20px” and then overwrites it at each breakpoint, setting it three times in total. In contrast, the second example sets the default padding to “20px” and only overrides it at the relevant breakpoint where it isn’t the default value (in this instance, tablet is the exception).

The goal is to:

  • Only set styles when needed. 
  • Not set them with the expectation of overwriting them later on, again and again.

To this end, closed media query ranges are our best friend. If we need to make a change to any given view, we make it in the CSS media query range that applies to the specific breakpoint. We’ll be much less likely to introduce unwanted alterations, and our regression testing only needs to focus on the breakpoint we have actually edited. 

Taking the above example, if we find that .my-block spacing on desktop is already accounted for by the margin at that breakpoint, and since we want to remove the padding altogether, we could do this by setting the mobile padding in a closed media query range.

.my-block {
  @media (max-width: 767.98px) {
    padding: 20px;
  }
  @media (min-width: 768px) and (max-width: 1023.98px) {
    padding: 40px;
  }
}
The browser default padding for our block is “0,” so instead of adding a desktop media query and using unset or “0” for the padding value (which we would need with mobile-first), we can wrap the mobile padding in a closed media query (since it is now also an exception) so it won’t get picked up at wider breakpoints. At the desktop breakpoint, we won’t need to set any padding style, as we want the browser default value.

Bundling versus separating the CSS

Back in the day, keeping the number of requests to a minimum was very important due to the browser’s limit of concurrent requests (typically around six). As a consequence, the use of image sprites and CSS bundling was the norm, with all the CSS being downloaded in one go, as one stylesheet with highest priority. 

With HTTP/2 and HTTP/3 now on the scene, the number of requests is no longer the big deal it used to be. This allows us to separate the CSS into multiple files by media query. The clear benefit of this is the browser can now request the CSS it currently needs with a higher priority than the CSS it doesn’t. This is more performant and can reduce the overall time page rendering is blocked.

Which HTTP version are you using?

To determine which version of HTTP you’re using, go to your website and open your browser’s dev tools. Next, select the Network tab and make sure the Protocol column is visible. If “h2” is listed under Protocol, it means HTTP/2 is being used. 

Note: to view the Protocol in your browser’s dev tools, go to the Network tab, reload your page, right-click any column header (e.g., Name), and check the Protocol column.

Note: for a summarized comparison, see ImageKit’s “HTTP/2 vs. HTTP/1.”

Also, if your site is still using HTTP/1...WHY?!! What are you waiting for? There is excellent user support for HTTP/2.

Splitting the CSS

Separating the CSS into individual files is a worthwhile task. Linking the separate CSS files using the relevant media attribute allows the browser to identify which files are needed immediately (because they’re render-blocking) and which can be deferred. Based on this, it allocates each file an appropriate priority.

In the following example of a website visited on a mobile breakpoint, we can see the mobile and default CSS are loaded with “Highest” priority, as they are currently needed to render the page. The remaining CSS files (print, tablet, and desktop) are still downloaded in case they’ll be needed later, but with “Lowest” priority. 

With bundled CSS, the browser will have to download the CSS file and parse it before rendering can start.

While, as noted, with the CSS separated into different files linked and marked up with the relevant media attribute, the browser can prioritize the files it currently needs. Using closed media query ranges allows the browser to do this at all widths, as opposed to classic mobile-first min-width queries, where the desktop browser would have to download all the CSS with Highest priority. We can’t assume that desktop users always have a fast connection. For instance, in many rural areas, internet connection speeds are still slow. 

The media queries and number of separate CSS files will vary from project to project based on project requirements, but might look similar to the example below.

Bundled CSS

<link href="site.css" rel="stylesheet">

This single file contains all the CSS, including all media queries, and it will be downloaded with Highest priority.

Separated CSS

<link href="default.css" rel="stylesheet"><link href="mobile.css" media="screen and (max-width: 767.98px)" rel="stylesheet"><link href="tablet.css" media="screen and (min-width: 768px) and (max-width: 1083.98px)" rel="stylesheet"><link href="desktop.css" media="screen and (min-width: 1084px)" rel="stylesheet"><link href="print.css" media="print" rel="stylesheet">

Separating the CSS and specifying a media attribute value on each link tag allows the browser to prioritize what it currently needs. Out of the five files listed above, two will be downloaded with Highest priority: the default file, and the file that matches the current media query. The others will be downloaded with Lowest priority.

Depending on the project’s deployment strategy, a change to one file (mobile.css, for example) would only require the QA team to regression test on devices in that specific media query range. Compare that to the prospect of deploying the single bundled site.css file, an approach that would normally trigger a full regression test.

Moving on

The uptake of mobile-first CSS was a really important milestone in web development; it has helped front-end developers focus on mobile web applications, rather than developing sites on desktop and then attempting to retrofit them to work on other devices.

I don’t think anyone wants to return to that development model again, but it’s important we don’t lose sight of the issue it highlighted: that things can easily get convoluted and less efficient if we prioritize one particular device—any device—over others. For this reason, focusing on the CSS in its own right, always mindful of what is the default setting and what’s an exception, seems like the natural next step. I’ve started noticing small simplifications in my own CSS, as well as other developers’, and that testing and maintenance work is also a bit more simplified and productive. 

In general, simplifying CSS rule creation whenever we can is ultimately a cleaner approach than going around in circles of overrides. But whichever methodology you choose, it needs to suit the project. Mobile-first may—or may not—turn out to be the best choice for what’s involved, but first you need to solidly understand the trade-offs you’re stepping into.