Skip to content

refactor(frontend): Replace Tailwind with StyleX - #408

Merged
nsdeschenes merged 16 commits into
mainfrom
nd/stylex-pill
Oct 26, 2026
Merged

nsdeschenes merged 16 commits into
mainfrom
nd/stylex-pill

Conversation

@nsdeschenes

@nsdeschenes nsdeschenes commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Times are a changing, and who doesn't love a good ol'refactor. The goal of this PR is to migrate away from Tailwind, and instead over to StyleX. The reason for this is that we live in a world of agents now, and constant validation is key to ensuring durable, and good code. StyleX provides this via type safe styles unlike those of Tailwind, which at the end of the day are just strings.

🤖 generated description below:

Why

Replace Tailwind throughout the frontend with StyleX. Shared components accept compiled styles through typed style props, so callers can compose overrides without class strings or raw CSS objects.

What changed

  • Migrate shared components and all incident, filter, and availability screens. Remove Tailwind, CVA, classnames, tailwind-merge, and cn.
  • Generate only tokens.stylex.ts from Scraps colors, spacing, radii, and typography. Store direct values with dark-mode media overrides. Remove scraps-theme.css and the intermediate variable aliases.
  • Add the StyleX ESLint plugin, a rule that requires StyleX styles, and negative type and lint tests. Preserve calendar selection, keyboard focus, dynamic dimensions, and dialog scroll locking.

Scope

The change covers frontend application styling. The backend and API contracts are unchanged. No demo page remains.

Global CSS retains font faces and the latest Tailwind Preflight, adapted as standalone CSS in the reset layer. All native inputs suppress focus outlines through a global reset rule. Buttons, links, selects, and textareas retain native focus. Calendar adapters use generated classes internally, and Radix manages its runtime positioning.

Tradeoffs

Scraps exports ordinary JavaScript objects that the StyleX compiler cannot import directly as tokens. A generator converts those values into a StyleX module and checks the committed output during lint. StyleX emits the CSS declarations. Preflight uses standard CSS font defaults in place of its Tailwind compiler helpers. Its MIT notice ships with the production assets.

Blast radius

Every frontend screen uses the new styling path. Review the global reset, calendar adapters, dynamic measurements, and conditional style precedence closely.

Verification

  • All 193 frontend tests pass, including six style contract tests. Lint, formatting, type checking, token freshness, the production build, and applicable precommit and pre-push hooks pass.
  • All 118 tokens preserve their previous values in both color schemes through the installed StyleX compiler. Production CSS contains no Tailwind symbols or Scraps variable aliases.
  • Browser checks cover desktop and mobile layouts, light and dark rendering, calendars, keyboard focus return, dialogs, scroll unlock, and textarea resizing. Visual checks are sampled. Dark hover precedence was checked through compiled CSS, while RTL and week numbers have component test coverage. Browser checks confirm unchanged dropdown geometry at 1280px, outline removal on all five searchable dropdowns and a regular form input, and native focus on selects, textareas, and buttons.

Comment thread frontend/package.json Resolved
@nsdeschenes nsdeschenes changed the title refactor(frontend): migrate Pill to StyleX refactor(frontend): replace Tailwind with StyleX Oct 20, 2026

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread frontend/src/components/Calendar.tsx Resolved
@nsdeschenes nsdeschenes changed the title refactor(frontend): replace Tailwind with StyleX refactor(frontend): Replace Tailwind with StyleX Oct 20, 2026
Comment thread frontend/src/components/buttonStyles.ts Resolved
Comment thread frontend/src/components/ErrorState.tsx Resolved
Restore the former input outline rule through StyleX in the tag editor and filters. The migration omitted their focus:outline-none declarations.

Browser comparisons at 1280px and 850px confirm that dropdown geometry and scroll match Tailwind. All 193 frontend tests and applicable pre-commit and pre-push checks pass.
Replace the partial reset with current upstream Preflight in the reset layer. Expand its font helpers into standard CSS and distribute the MIT notice with the frontend assets.

Remove the four inline input outline overrides to restore native browser focus. Verify upstream declaration parity, built CSS, dropdown geometry, calendars, forms, and all 193 frontend tests.
Restore the StyleX equivalent of focus:outline-none on the four inline dropdown inputs. Preflight remains the shared reset and other controls keep native focus.

Browser checks fail before and pass after the change, with unchanged geometry and scrolling. All 193 frontend tests and required checks pass.
Compose dropdownInputStyles.focus in the tag editor and filters instead of defining the same focus rule in four places.

Production CSS remains identical. All four inputs pass browser checks, and all 193 frontend tests and required hooks pass.
Suppress focus outlines for all inputs through the reset layer. Remove the shared dropdown helper so inputs receive the default automatically.

Verified all five searchable dropdowns and a regular form input in the browser. Selects, textareas, and buttons retain native focus. All 193 frontend tests, lint, formatting, the production build, and applicable pre-commit and pre-push checks pass.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread frontend/src/styles/index.css Resolved
Wrap the incident list and availability error descriptions in paragraphs so their text, help link, and punctuation stay together within the flex layout.

Verified both error screens in the browser and ran all 193 frontend tests and required checks.
Replace fixed neutral border colors across shared controls and dropdowns with border.secondary so borders follow the Scraps light and dark themes.

Verified representative controls in the browser and both token values in production CSS. Existing focus behavior is unchanged. All 193 frontend tests and required checks pass.
Comment thread frontend/src/components/Calendar.tsx Resolved
Comment thread frontend/src/components/Calendar.tsx Resolved
@nsdeschenes
nsdeschenes marked this pull request as ready for review October 21, 2026 15:54
@nsdeschenes
nsdeschenes requested a review from a team as a code owner October 21, 2026 15:54

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread frontend/src/styles/index.css Resolved
Preserve the incoming branded Statuspage timeline icon and dark-mode inversion through StyleX. Retain the newer feature-branch input focus and downtime changes.

All 194 frontend tests, type checking, lint, formatting, the production build, and required pre-commit checks pass. Affected backend database tests could not initialize because local PostgreSQL is unavailable.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ae49ece. Configure here.

Comment thread frontend/src/components/Popover.tsx Resolved
Comment on lines 169 to 173

const handleKeyDown = (event: KeyboardEvent) => {
// Enter to save (only for single-line, or Ctrl/Cmd+Enter for multiline)
if (event.key === 'Enter') {
if (!multiline || event.ctrlKey || event.metaKey) {
event.preventDefault();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Global Enter handler saves while other controls are focused

While editing, the document-level Enter listener saves without checking which element is focused. Pressing Enter on Cancel can therefore save the draft instead of cancelling; repeated Enter events can also invoke onSave concurrently because save() has no isSaving guard. Handle Enter on the editor input and prevent duplicate saves while one is pending.

Evidence
  • While editing, EditableTextField registers a document-level keydown listener that calls save() for Enter in single-line mode, or Ctrl/Cmd+Enter in multiline mode.
  • The listener does not check event.target; it prevents the default and calls save() even when focus is on Cancel, so keyboard activation of Cancel can be suppressed.
  • save() sets isSaving but does not check it before calling onSave, so repeated qualifying keydowns can start concurrent saves; the existing tests cover clicking Cancel and Save, but not these keyboard cases.

Identified by Warden · find-bugs · HCC-GG4

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These issues existed before the migration, instead i filed issues for them to be tackled at a later date:

@nsdeschenes
nsdeschenes merged commit 7b80238 into main Oct 26, 2026
24 checks passed
@nsdeschenes
nsdeschenes deleted the nd/stylex-pill branch October 26, 2026 14:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants