Software Design & Development - User Experience & Interface Design

A Backend Dev’s First Stack Test Is the Exit Cost

A backend developer evaluating a UI stack can mistake a polished component gallery for evidence that the team can maintain the product built with it. I would choose the library whose failures my team can diagnose and repair, even if another library produces a better-looking demo sooner. A trial should therefore include a broken interaction, an accessibility defect, and a replacement exercise—not just a successful build.

A broken screen tells you more than a component gallery

Give each candidate the same small application: an authenticated page with an editable table, a save action, and a server response that can fail. Use an OAuth 2.0 or OpenID Connect login already familiar to your backend team, and keep the API contract identical across trials. Otherwise, differences in authentication and endpoint design will obscure differences in the UI libraries.

The application needs an inconvenient state. Have the API return a field-level validation error after an optimistic edit. Then interrupt a second request and reload the page. Watch whether the UI preserves the user’s work, announces the error, and settles into a state a developer can explain. A component that renders a convincing table but makes those transitions hard to control is a poor fit for an application with frequent edits.

For a local trial, allocate 2 developer-days per candidate; that is a planning value to tune, not a claim about how long integration normally takes. Give both candidates the same acceptance notes and record time spent reading documentation, writing workarounds, and diagnosing failures separately. Those categories matter because a fast initial render can conceal a slow repair cycle.

I would not rank candidates by the number of components in their galleries, because a component count says little about whether your team can repair the state transitions it will actually ship. The estimation argument Test the UI Framework Before You Estimate the Build makes a reasonable case for early experiments, but I would use the experiment to establish who owns a failure before using it to forecast delivery time.

The winning abstraction is the one you can bypass

Compare Material UI 7 with shadcn/ui backed by Radix UI on the same React 19 application. Material UI wins when its established components closely match your interaction and visual requirements: you can assemble common screens with fewer local component decisions. Its cost is accepting more of its styling and component conventions, and some advanced MUI X components have separate licence terms that require checking before adoption.

shadcn/ui with Radix UI wins when the team expects to change component markup and behaviour often, because the generated component code lives in the application and Radix supplies accessible interaction primitives. Its cost is ownership: copied components, design tokens, and upgrades become your team’s maintenance work. Neither choice is universally safer; the meaningful difference is where the repair work lands.

Build the first candidate from a pinned starting point rather than a moving template. This shell sequence creates a React TypeScript project with Vite 6, installs Material UI 7, and verifies that the initial application builds:

npm create vite@6 ui-trial -- --template react-ts
cd ui-trial
npm install
npm install @mui/material@7 @emotion/react @emotion/styled
npm run build
npm run dev -- --host 127.0.0.1

That successful build is only the starting line. Implement the same table and failure states in a separate shadcn/ui trial, recording the exact Radix packages and generated files used. Versions matter here because a comparison made against different React or Vite releases cannot cleanly attribute a regression to the component choice.

Then make one deliberately awkward change in each implementation: move the error message, change the keyboard behaviour of a menu, or replace the table’s edit control. Set 30 minutes as an adjustable investigation limit before asking for help. Record whether the answer came from public documentation, source code, a local override, or vendor support. A library you can bypass safely has a more credible maintenance story than one that works only while your requirements resemble its examples.

Accessibility failures reveal who really maintains the UI

Run the same keyboard route through both trials: open the edit control, submit an invalid value, find the error, correct the value, and leave the control. Test with a screen reader as well as a keyboard, because automated checks cannot establish whether an error announcement is understandable in context. Use axe-core to catch detectable issues, then check the relevant WCAG 2.2 AA criteria manually against the rendered interaction.

A practical acceptance set is 3 critical flows—successful edit, rejected edit, and interrupted save—but that count is a local threshold to adjust to the product. For each flow, note the focused element and accessible name before and after the state change. If the error appears visually but focus disappears into the page, the problem belongs in the vendor assessment even if the screenshot looks finished.

Use Playwright to repeat the navigation and network-failure steps, while keeping a short manual screen-reader pass for announcements. Playwright’s request interception can make the rejected save reproducible; a real screen reader can tell you whether the message is useful. Those are different forms of evidence, so passing one should not be reported as passing the other.

Ask a sharper question than “Is this component accessible?”: Can our developer identify and fix an accessibility regression without replacing the whole interaction? Inspect where focus management and ARIA attributes originate, whether the library documents its keyboard contract, and whether an override survives a package update. If a vendor promises compliance, request evidence for the specific component version you intend to install, because a broad product claim does not verify your edited implementation.

Licences and support terms matter at the point of failure

Check the package boundary against the application you built, not against a generic procurement list. If the table trial depends on a MUI X Data Grid Pro feature, inspect the applicable licence and support terms for that feature; the licence of the base Material UI package does not answer the question. For shadcn/ui, inspect the licences of the generated code and the Radix packages you actually import, along with the team effort needed to keep copied components current.

The cost warning Before You Scope It, Test the Stack’s Hidden Costs belongs in this review, but I would investigate a blocked fix before comparing subscription totals, because an inexpensive dependency is still costly when nobody can resolve a production defect. Give the vendor or maintainer a real, narrowly described issue from the trial and assess the answer: does it identify an API, a documented workaround, a known limitation, or merely a request to reproduce the problem again?

Run npm audit --omit=dev on each installed application, but treat its output as an investigation queue rather than a vendor score, because a reported transitive vulnerability may have no reachable path in your deployed UI. Check release notes and the package lockfile for the affected version before deciding whether an update is available. Also confirm whether support covers your deployed version, since an answer that requires an unplanned major upgrade may not help during an incident.

Before committing, replace one library-provided control with a plain React implementation behind the same application interface. If that swap changes API calls, validation rules, and page layout at once, your code has coupled product behaviour to the vendor. That is a reason to narrow the dependency boundary now, while the trial is small.

A decision record should make rejection possible

Write a short decision record with evidence from the two trials. Name the application commit, package versions, failing test, accessibility findings, workaround, and remaining uncertainty. Include a link to the vendor response if support was involved. A claim such as “easier to use” is too vague to survive a later dispute; “the rejected-save error required an undocumented focus override” gives another developer something to verify.

Define the rejection rule before viewing the final demos. For example, reject a candidate if the team cannot restore keyboard focus after a failed save without an override it cannot test, because that defect affects a critical flow and leaves maintenance dependent on guesswork. Treat performance separately: Google’s published “good” boundary for Interaction to Next Paint is 200 milliseconds, but measure your own interaction on representative hardware before attributing a slow result to the UI library. Network delay, application state, and your own rendering code can also cause it.

A backend developer’s first advantage in this evaluation is familiarity with contracts and failure modes. Apply that discipline to the interface: specify the state, force the failure, and identify who can repair it. The preferred vendor is the one that leaves your team with an understandable route from bug report to fix.

Start by creating one endpoint that rejects a table edit with a field-level error, then implement that single path in both candidates. Keep the request and response identical. Before adding another screen, ask a teammate unfamiliar with the trial to trace the error message and keyboard focus through the code. Their ability to find and change the behaviour is the first useful result of your evaluation.