CSS Layout
“Multi-concern UI separates navigation state, loading state, and animation state as independent dimensions that layer, not merge.”
Read before this build:
Rich Interactive UI + CSS Layout Fundamentals →Your task
Overview
Plant Floor Monitor can already fetch and render device data, but the screen still reads like a stacked prototype instead of a dashboard an operator could scan. An automations company wants to see whether you can turn the existing React component tree into a deliberate application layout with plain CSS, using Grid for the two-dimensional shell and Flexbox or nested Grid where one-dimensional alignment is the real problem. Your task is to upgrade the existing screen into a dashboard with a fixed sidebar, a fluid main region, responsive device cards, and status styling that can be defended in code review.
What You Should Build
- Update
src/App.tsxso the existing loading, error, and ready states render inside a real dashboard shell with a sidebar region and a main content region - Update the existing device-list and device-card markup as needed so
src/App.csscan lay the ready state out as a responsive grid of cards instead of a single stacked list - Add plain CSS in
src/App.cssfor the dashboard shell, the device-card layout, and a responsive card grid that usesrepeat(auto-fill, minmax(280px, 1fr)) - Keep the status badge styling driven by CSS custom properties so the three device states do not rely on repeated hardcoded color values
- Be prepared to explain why
auto-fillwithminmax(280px, 1fr)responds to viewport width without a media query, and what breaks if the minimum is changed to0
Constraints
- Stay within layout and styling plus the minimal JSX changes required to support that styling, do not add filters, rail interactions, reducer state, accessibility polish, or new fetch logic in this scenario
- Use plain CSS in
src/App.css, not Tailwind, CSS-in-JS, or a new design-token system - Keep
src/App.cssas the styling target because this work belongs to the Plant Floor Monitor surface, not to global document styles insrc/index.css - Reuse the existing
DeviceList,DeviceCard, andStatusBadgeboundaries instead of collapsing them back into one component - You are free to choose the exact JSX structure and class names that support the layout, as long as the finished screen satisfies the dashboard shell, responsive grid, and status-token requirements
- Treat the three device status states as a closed set in CSS just as they are in TypeScript, with one tokenized styling approach rather than three unrelated badge implementations
How to Approach This
The Core Insight
The current React boundaries are already doing the right jobs. App.tsx owns the async screen branch, DeviceList owns the collection, DeviceCard owns one record, and StatusBadge owns the closed-set status display. The missing piece is layout architecture. Right now the UI is readable, but it does not yet communicate hierarchy, density, or scan order like a dashboard. This scenario is about making the CSS structure match the component structure.
The Mental Model
Treat the page like a plant-floor wallboard mounted on a frame. The frame decides which large zones exist, the panels inside those zones decide how groups are arranged, and each indicator inside a panel decides how one signal is presented. That leads directly to the primitive choice:
- CSS Grid for the frame and for card collections, because those are two-dimensional placement problems
- Flexbox or nested Grid inside one card row, because aligning a title block against one badge is a one-dimensional problem
How to Decompose This
Before you touch the CSS, answer three questions:
- Which part of the screen is truly two-dimensional and therefore belongs to Grid?
- Which selectors need to exist in JSX before the CSS can target them cleanly?
- Why should the status colors become CSS custom properties instead of three repeated literal color pairs?
Building It
Project state entering this scenario is now concrete. Earlier steps already established the API contract, the fetch layer, the cancellable useDevices() hook, and the rendering split across App.tsx, DeviceList, DeviceCard, and StatusBadge. The current src/App.css gives those components a readable first pass, but the screen is still mostly a centered stack. This scenario upgrades that same surface into a dashboard layout without adding new product behavior. The lesson is CSS architecture, not more React state.
Step 1: Picture the target before choosing the markup
Start by deciding what the finished screen should look and feel like. You already have working data flow and working components. What is missing is a more deliberate spatial structure.
The target screen should read roughly like this on desktop:
On narrower screens, the page should stack into one column while the cards continue to reflow:
That picture is the requirement. You can satisfy it with whatever JSX structure feels most natural, as long as the layout contract is clear and the existing component boundaries remain sensible.
Step 2: Separate visual goals from implementation choices
The interviewer is not really asking for one exact DOM tree. They are asking whether you can reason from layout requirements to the right CSS primitive. The useful decomposition is:
- The outer page frame needs two major regions on desktop and one region per row on mobile
- The device collection needs a card grid that adds or removes columns as width changes
- Each device card needs a small internal layout so title, metadata, and badge do not fight for space
- The status badge needs shared structure plus small state-specific value changes
Those are outcome statements, not implementation instructions. You are free to decide whether the shell uses main plus aside, two section elements, or another equivalent structure. What matters is that the result exposes two page regions, a responsive card grid, and a badge system that does not repeat itself.
Step 3: Know what the card grid must prove
The most important layout behavior in this scenario is the device grid. You do not need a breakpoint to say "at this width, switch from three columns to two." The browser can already derive that if the rule encodes the minimum viable card width.
Here is the behavior the finished grid should demonstrate:
- each card may grow to share leftover space with its neighbors
- each card may not shrink below a readable minimum
- when another full card no longer fits, the layout drops to fewer columns
That is why repeat(auto-fill, minmax(280px, 1fr)) matters. It expresses the contract directly:
280pxis the floor below which the card should stop shrinking1frlets the occupied tracks expand evenlyauto-fillkeeps adding tracks while the container has room for another 280-pixel card
The key teaching point is that the browser performs this math continuously. No media query is needed because the rule already says when another column is valid.
This is also the clearest place to explain what breaks with minmax(0, 1fr). If the minimum becomes 0, the browser is free to preserve more columns than the design can really support. Instead of dropping to fewer columns, the cards can compress until the content starts to feel broken:
- card titles wrap too early
- metadata lines become cramped
- the badge can collide visually with the title area
- the grid technically fits, but the design stops being readable
So the question is not "does minmax(0, 1fr) work syntactically?" It does. The question is whether it encodes the right layout constraint. For the card grid, it does not.
Step 4: Use a small visual spec for the card itself
You do not need prescribed JSX here either. What you need is a target composition for one card.
The implementation can vary, but a reviewer should still be able to see:
- a top region where identity and badge align cleanly
- a body region where supporting metadata stacks without crowding
- enough spacing that the card reads like one dashboard unit instead of loose text inside a box
This is where Flexbox often wins for the header row. It solves a one-dimensional alignment problem. The card as a whole may still use Grid or block flow with spacing, depending on how you want to organize its internal regions.
Step 5: Treat badge colors like tokens, not one-off literals
The status system is a closed set: online, offline, and alarm. That means the badge styling should also behave like a closed system rather than like three disconnected exceptions.
The visual goal is simple:
What should stay shared across all three:
- badge shape
- badge padding
- badge typography
- uppercase treatment
- border radius
What should vary by state:
- background color
- text color
That is why CSS custom properties are a better fit than repeating literal colors in three separate blocks. You can keep one base badge rule and let each state-specific selector define only the values that change. The exact variable names are not the lesson. The lesson is that the closed-set states should supply values into a shared badge contract instead of re-declaring the whole badge each time.
Step 6: Put the breakpoint where the page actually needs it
The card grid should solve itself from the auto-fill and minmax(280px, 1fr) rule. The page shell is different because it owns the large-scale region split.
The outcome to aim for is:
- desktop: sidebar beside main content
- smaller screens: sidebar above main content
That is a good use of one media query, because it changes the page-region structure itself. The lesson here is not "never use media queries." It is "do not use a media query to do work the grid track definition can already do."
Why This Way
This approach teaches you to reason from visible requirements instead of copying an implementation. The scenario still has strong technical constraints, but they are expressed as layout outcomes and reviewable behavior.
Grid belongs at the shell and collection levels because those are two-dimensional placement decisions. Flexbox often belongs inside the card header because that is a one-dimensional alignment decision. The distinction matters because it trains you to pick a primitive that matches the problem instead of reaching for one pattern everywhere.
repeat(auto-fill, minmax(280px, 1fr)) is strong because it encodes both the minimum acceptable card width and the rule for sharing extra space. The browser can therefore add or remove columns continuously as the container changes width, which is why the layout responds without separate breakpoint logic.
minmax(0, 1fr) is correct when the goal is "let this track shrink as much as needed to avoid overflow," such as the fluid main column beside a fixed sidebar. It is incorrect for the device card grid because it removes the readability floor. Once the minimum becomes zero, the layout is allowed to preserve too many columns by crushing the cards instead of wrapping them.
CSS custom properties improve the badge states because the status styles are a closed set with shared structure. The base rule owns the badge shape and reads named values. Each status modifier only supplies the values that differ. That is easier to review, easier to extend, and much less fragile than copying literal color pairs into multiple selectors.
How to Explain It
I started from the target screen shape instead of from one exact DOM structure. The page shell is a Grid problem because it places two major regions on the page. The device list is also a Grid problem because it needs to create or remove columns as width changes. The card header is usually a Flexbox problem because it aligns identity content against a badge on one axis. I used repeat(auto-fill, minmax(280px, 1fr)) because it preserves a real minimum card width while letting the browser add or remove columns automatically. I did not use minmax(0, 1fr) for the cards because that would preserve extra columns by crushing the cards below their readable size. For badge states, I kept one shared badge contract and let the state-specific rules provide only the colors that differ.
Checkpoint
- Why is
minmax(0, 1fr)useful for the fluid main shell column but harmful for the device-card grid? - What repeated maintenance problem disappears once the badge modifiers only set
--status-bgand--status-textinstead of repeating fullbackgroundandcolordeclarations plus base badge structure?
Check your understanding
Defend the layout architecture in code review terms. Why is the shell a Grid problem, why does `repeat(auto-fill, minmax(280px, 1fr))` respond without a media query, what concrete UI regression appears if the card grid switches to `minmax(0, 1fr)`, and why are CSS custom properties a better fit than repeated literal color values for the three badge states?