Skip to content

refactor(button): extract repeated tri-state patterns into mixins#3372

Open
orrgottlieb wants to merge 1 commit into
masterfrom
refactor/button-css-matrix
Open

refactor(button): extract repeated tri-state patterns into mixins#3372
orrgottlieb wants to merge 1 commit into
masterfrom
refactor/button-css-matrix

Conversation

@orrgottlieb

@orrgottlieb orrgottlieb commented May 22, 2026

Copy link
Copy Markdown
Contributor

User description

Summary

Button.module.scss is a 3 (kind) × 9 (color) × ~3 (state) combinatorial matrix. Five recurring patterns accounted for the bulk of the file's repetition. Each is now a SCSS mixin defined once and applied per color.

Mixins

Mixin Used by
solid-tristate(class, bg, bg-active, fg?) kindPrimary regular colors (primary, brand, positive, negative, inverted, onInvertedBackground)
outlined-tristate(class, color, bg-active) kindSecondary (positive, negative, inverted, onInvertedBackground)
flat-tristate(class, color, bg-active) kindTertiary (positive, negative, inverted, onInvertedBackground)
brightness-overlay-tristate(class, literal-bg?) onPrimaryColor / fixedLight on all kinds (extra literal-bg param for kindPrimary #e6e9ef overlay)
disabled-with-opacity(class, fg) onPrimaryColor / fixedLight / fixedDark on all kinds

Result: 578 → 457 lines (-121, ~21% smaller source) with no semantic change.

Equivalence verification

I diffed the compiled CSS string in dist/Button/Button.module.scss.js against master's. The only differences are:

  1. Two source rules with identical bodies (A { x } B { x }) merged into one (A, B { x }) — identical CSS cascade.
  2. Whitespace within selector lists (multi-line vs single-line) — purely cosmetic.

Class-name hashes (button_2f1d87d372, kindPrimary_306927aec1, etc.) are byte-identical to master. No DOM, snapshot, or visual regressions possible from this change.

Test plan

  • yarn workspace @vibe/button build succeeds
  • Compiled CSS is logically equivalent to master (verified via normalized rule-set diff — see commit message)
  • Class-name → hash dictionary is unchanged (verified by extracting the JSON map from both builds)
  • CI green (snapshot tests, lint, full test suite)

🤖 Generated with Claude Code


PR Type

Enhancement


Description

  • Extract five recurring SCSS patterns into reusable mixins

  • Reduce Button.module.scss from 578 to 457 lines (~21% reduction)

  • Consolidate tri-state (Active/hover/focus) styling logic

  • Maintain identical compiled CSS and class-name hashes


Diagram Walkthrough

flowchart LR
  A["Repetitive tri-state<br/>CSS rules"] -->|"Extract patterns"| B["5 SCSS mixins:<br/>solid/outlined/flat<br/>brightness-overlay<br/>disabled-with-opacity"]
  B -->|"Apply per color"| C["Consolidated<br/>Button.module.scss<br/>-121 lines"]
  C -->|"Compile"| D["Identical CSS<br/>& class hashes"]
Loading

File Walkthrough

Relevant files
Enhancement
Button.module.scss
Refactor tri-state patterns into reusable SCSS mixins       

packages/components/button/src/Button/Button.module.scss

  • Added five SCSS mixins at file top: solid-tristate, outlined-tristate,
    flat-tristate, brightness-overlay-tristate, disabled-with-opacity
  • Replaced 450+ lines of repetitive color/state rules with mixin
    invocations across kindPrimary, kindSecondary, kindTertiary blocks
  • Consolidated selector groups (e.g., .colorPositiveActive,
    .colorPositive:hover, .colorPositive:focus) into mixin-generated rules
  • Maintained all CSS semantics and visual behavior with zero functional
    changes
+257/-378

The Button.module.scss matrix is a 3 (kind) × 9 (color) × ~3 (state)
combinatorial of mostly mechanical rules. Five recurring patterns
account for the bulk of the file:

- solid-tristate     — bg + Active/hover/focus bg (kindPrimary regular colors)
- outlined-tristate  — fg + border-color + Active/hover/focus bg (kindSecondary)
- flat-tristate      — fg + Active/hover/focus bg (kindTertiary)
- brightness-overlay — Active/hover/focus backdrop-filter brightness + focus-style-on-primary
- disabled-with-opacity — opacity + fg, used by overlay-style colors

These are now defined as mixins at the top of the file and applied per
color, dropping ~120 lines of repetition (635 → 257 inserts) without
any change to compiled CSS semantics or class-name hashes.

Equivalence verified by diffing the compiled CSS: the only differences
are `A {x} B {x}` rules merged into `A, B {x}` (identical cascade).
@orrgottlieb orrgottlieb requested a review from a team as a code owner May 22, 2026 07:38
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1)

Grey Divider


Remediation recommended

1. #e6e9ef hardcoded in mixin 📘 Rule violation ⚙ Maintainability
Description
The updated Button.module.scss passes the hardcoded color #e6e9ef into
brightness-overlay-tristate instead of using a design token. This can break theme consistency and
violates the design-tokens requirement for CSS Modules styling.
Code

packages/components/button/src/Button/Button.module.scss[R222-230]

Evidence
PR Compliance ID 4 requires using design tokens (and avoiding non-token styling values where
expected). The changed code explicitly uses the literal hex value #e6e9ef in the module stylesheet
when applying brightness-overlay-tristate.

CLAUDE.md: Use CSS Modules and Design Tokens; Disallow Imports in .module.scss Files
packages/components/button/src/Button/Button.module.scss[222-230]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Button.module.scss` uses a hardcoded hex color (`#e6e9ef`) for the brightness overlay background rather than a design token / CSS variable.

## Issue Context
Rule 4 requires styles to use design tokens where expected. The hardcoded value is currently passed into `brightness-overlay-tristate(...)` for `colorOnPrimaryColor` and `colorFixedLight`.

## Fix Focus Areas
- packages/components/button/src/Button/Button.module.scss[222-230]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@github-actions

Copy link
Copy Markdown
Contributor

📦 Bundle Size Analysis

✅ No bundle size changes detected.

Unchanged Components
Component Base PR Diff
@vibe/button 17.3KB 17.3KB +1B 🔺
@vibe/clickable 5.95KB 5.96KB +3B 🔺
@vibe/dialog 52.14KB 52.16KB +14B 🔺
@vibe/icon-button 66.09KB 66.03KB -59B 🟢
@vibe/icon 12.92KB 12.89KB -32B 🟢
@vibe/layer 2.96KB 2.96KB 0B ➖
@vibe/layout 9.82KB 9.83KB +11B 🔺
@vibe/loader 5.64KB 5.65KB +10B 🔺
@vibe/tooltip 61.33KB 61.32KB -7B 🟢
@vibe/typography 63.47KB 63.43KB -47B 🟢
Accordion 6.31KB 6.29KB -14B 🟢
AccordionItem 66.43KB 66.39KB -47B 🟢
AlertBanner 70.83KB 70.74KB -97B 🟢
AlertBannerButton 18.76KB 18.77KB +5B 🔺
AlertBannerLink 15.26KB 15.26KB +4B 🔺
AlertBannerText 63.95KB 63.91KB -38B 🟢
AttentionBox 74.35KB 74.3KB -57B 🟢
Avatar 66.84KB 66.72KB -119B 🟢
AvatarGroup 93.29KB 93.27KB -17B 🟢
Badge 43.19KB 43.17KB -24B 🟢
BreadcrumbItem 64.7KB 64.64KB -61B 🟢
BreadcrumbMenu 68.57KB 68.58KB +12B 🔺
BreadcrumbMenuItem 77.07KB 77KB -73B 🟢
BreadcrumbsBar 5.68KB 5.68KB -1B 🟢
ButtonGroup 68.32KB 68.33KB +11B 🔺
Checkbox 66.83KB 66.92KB +99B 🔺
Chips 75.05KB 75.14KB +92B 🔺
ColorPicker 74.47KB 74.52KB +46B 🔺
ColorPickerContent 73.73KB 73.72KB -5B 🟢
Combobox 84.08KB 83.95KB -126B 🟢
Counter 42.21KB 42.28KB +65B 🔺
DatePicker 112.41KB 112.37KB -44B 🟢
Divider 5.42KB 5.46KB +44B 🔺
Dropdown 95.35KB 95.11KB -242B 🟢
EditableHeading 66.63KB 66.53KB -104B 🟢
EditableText 66.46KB 66.42KB -38B 🟢
EmptyState 70.48KB 70.43KB -51B 🟢
ExpandCollapse 66.22KB 66.19KB -32B 🟢
FormattedNumber 5.86KB 5.84KB -13B 🟢
GridKeyboardNavigationContext 4.65KB 4.65KB -4B 🟢
HiddenText 5.4KB 5.39KB -15B 🟢
Info 72.06KB 72.04KB -17B 🟢
Label 68.65KB 68.67KB +21B 🔺
Link 14.91KB 14.88KB -30B 🟢
List 72.88KB 72.84KB -43B 🟢
ListItem 65.54KB 65.49KB -59B 🟢
ListItemAvatar 66.88KB 66.92KB +40B 🔺
ListItemIcon 13.97KB 13.97KB +5B 🔺
ListTitle 65.02KB 64.95KB -69B 🟢
Menu 8.65KB 8.64KB -19B 🟢
MenuDivider 5.56KB 5.57KB +7B 🔺
MenuGridItem 7.16KB 7.19KB +36B 🔺
MenuItem 76.95KB 76.96KB +6B 🔺
MenuItemButton 70.11KB 69.97KB -149B 🟢
MenuTitle 65.35KB 65.34KB -11B 🟢
MenuButton 66.08KB 66.14KB +57B 🔺
Modal 79.14KB 79.06KB -82B 🟢
ModalContent 4.72KB 4.71KB -1B 🟢
ModalHeader 65.79KB 65.77KB -19B 🟢
ModalMedia 7.51KB 7.5KB -3B 🟢
ModalFooter 67.72KB 67.67KB -44B 🟢
ModalFooterWizard 68.6KB 68.55KB -51B 🟢
ModalBasicLayout 8.96KB 8.9KB -57B 🟢
ModalMediaLayout 8.08KB 8.06KB -19B 🟢
ModalSideBySideLayout 6.3KB 6.29KB -4B 🟢
MultiStepIndicator 52.96KB 52.95KB -11B 🟢
NumberField 72.87KB 72.82KB -58B 🟢
ProgressBar 7.34KB 7.35KB +7B 🔺
RadioButton 65.9KB 65.9KB +3B 🔺
Search 70.65KB 70.57KB -83B 🟢
Skeleton 6KB 6.01KB +4B 🔺
Slider 73.86KB 73.84KB -19B 🟢
SplitButton 66.48KB 66.42KB -62B 🟢
SplitButtonMenu 8.8KB 8.76KB -34B 🟢
Steps 71.31KB 71.33KB +25B 🔺
Table 7.26KB 7.25KB -13B 🟢
TableBody 66.68KB 66.69KB +11B 🔺
TableCell 65.22KB 65.26KB +36B 🔺
TableContainer 5.31KB 5.32KB +16B 🔺
TableHeader 5.64KB 5.64KB +1B 🔺
TableHeaderCell 72.2KB 72.1KB -100B 🟢
TableRow 5.56KB 5.55KB -8B 🟢
TableRowMenu 68.87KB 68.85KB -27B 🟢
TableVirtualizedBody 71.42KB 71.38KB -39B 🟢
Tab 64KB 63.93KB -73B 🟢
TabList 8.89KB 8.86KB -30B 🟢
TabPanel 5.3KB 5.29KB -14B 🟢
TabPanels 5.86KB 5.86KB -2B 🟢
TabsContext 5.48KB 5.51KB +29B 🔺
TextArea 66.26KB 66.25KB -3B 🟢
TextField 69.43KB 69.43KB -2B 🟢
TextWithHighlight 64.35KB 64.3KB -48B 🟢
ThemeProvider 4.36KB 4.36KB -1B 🟢
Tipseen 71.17KB 71.15KB -23B 🟢
TipseenContent 71.6KB 71.61KB +7B 🔺
TipseenMedia 71.27KB 71.29KB +23B 🔺
TipseenWizard 73.93KB 73.84KB -94B 🟢
Toast 74.1KB 73.98KB -128B 🟢
ToastButton 18.59KB 18.58KB -8B 🟢
ToastLink 15.05KB 15.08KB +31B 🔺
Toggle 66.62KB 66.59KB -27B 🟢
TransitionView 5.42KB 5.45KB +30B 🔺
VirtualizedGrid 12.54KB 12.54KB +2B 🔺
VirtualizedList 12.28KB 12.26KB -12B 🟢
List (Next) 8.17KB 8.16KB -15B 🟢
ListItem (Next) 69.88KB 69.82KB -62B 🟢
ListTitle (Next) 65.31KB 65.29KB -21B 🟢

📊 Summary:

  • Total Base Size: 4.75MB
  • Total PR Size: 4.75MB
  • Total Difference: 2.19KB

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.

1 participant