Hi all,
One more UI thread, after Path to Vite, Path to Vitest and Path to TypeScript. This one removes a runtime dependency rather than swapping a tool, and it has merged.
What styled-components is
styled-components is CSS-in-JS: you write CSS in a JavaScript template literal, and the library parses it at runtime, hashes it into a class name and injects a <style> tag. The appeal is that the styling sits beside the component and can read its props, so a variant is an expression rather than a class name you have to remember to add.
What the UI had
Across awx/ui/src, 145 files imported it and defined 291 styled components. Another 76 elements carried a css="..." prop, which is the same thing in a different spelling, since babel-plugin-styled-components compiles each one into a generated component at build time. Almost none of it needed a runtime: most definitions were static CSS, and most of the rest switched on a boolean, leaving only a handful that computed an actual value.
What replaces it
151 plain CSS files, one beside each component. PatternFly 6 is already the styling system here, and its own components are styled with ordinary class selectors, so the UI now carries one styling system where it used to carry two. Three shapes cover nearly all of the move:
- Static rules became a class. A
styled.div holding display: flex becomes a CSS class.
- A boolean prop became a modifier class.
$isSelected and $hasErrors are variants, not logic.
- A per-instance value stayed on the element. An actions cell's width is a
style attribute.
The only genuinely dynamic case is the job output's ANSI palette, which grows as output arrives. That is a <style> element the wrapper renders, scoped to its own class.
What the runtime was hiding
Once the CSS sits in a file you can read, four things surfaced that were invisible while it was generated:
- Two rules the browser has always discarded. One missing semicolon, one naming no property at all.
- Seven props that never applied. PatternFly's own
display: grid won on ordering every time.
- Three plain divs with handlers. The accessibility rules could not see through a styled component.
- Four files that would have collided. Two
InstanceListItem, and two ContentLoading.
The first is worth spelling out. The job output toolbar's badge wrote background-color: ${props.color} with no semicolon before the next declaration, so the value parsed as #470000 color: white, which is not a colour, and the whole declaration was dropped. The failed and unreachable badges have never been red.
All four are carried across as they actually render rather than as they were written, so this change moves nothing on screen, including the parts that were arguably meant to.
How it is checked
Not by reading the diff, which is 334 files. Both builds were loaded in a browser against the same database, and every element on 24 routes compared across 49 computed properties:
529,968 property comparisons, zero differences, and no element missing on either side.
That measurement found all four problems above, over rounds of 950, 39 and 18 differences. Twice against one build gives zero, so the floor is real rather than a tolerance. The UI suite and the end-to-end tests are unchanged.
Pull requests