ShadCN UI Audit Normalization Plan And Fixes For Optimal Performance
Hey guys! We've recently conducted a thorough audit of our ShadCN UI implementation, and we've uncovered some areas we can normalize and optimize. This article outlines the findings, our proposed plan of action, and the minimal fixes we'll be implementing to ensure a consistent and accessible user interface. Let's dive in!
Understanding the UI Audit: ShadCN, Radix, and CVA
Our UI is built using a combination of powerful tools: ShadCN, Radix UI, and class-variance-authority (CVA). This stack gives us flexibility and control, but it's crucial to ensure these tools are working harmoniously. The audit highlighted a few key areas that need attention:
- Inline transform suppression conflicting with animations and
prefers-reduced-motion
. - Hard-coded
rgba
/hex colors bypassing our established theme tokens. - A form background token gap.
To address these findings systematically, we've created a normalization plan with phased implementation. This approach allows us to make improvements incrementally, minimizing risks and ensuring a smooth transition. This comprehensive plan ensures that our UI remains consistent, accessible, and performant.
References
For those interested in the nitty-gritty details, here are some references to specific files and lines of code where these issues were identified:
- ShadCN config: components.json
- Button (CVA + Radix Slot) with inline transform none: src/components/ui/button.tsx style at (src/components/ui/button.tsx:49)
- Dialog (Radix): src/components/ui/dialog.tsx
- Table: src/components/ui/table.tsx
- Form + a11y contexts (aria-describedby, aria-invalid): src/components/ui/form.tsx, aria wiring at (src/components/ui/form.tsx:111)
- Label (CVA): src/components/ui/label.tsx
- Alert (CVA): src/components/ui/alert.tsx
- DropdownMenu (Radix) with inline transform none: src/components/ui/dropdown-menu.tsx
- Input/Select using
bg-[hsl(var(--form-bg))]
while--form-bg
not defined in globals: src/components/ui/input.tsx, src/components/ui/select.tsx - Sonner Toaster with raw color classes: src/components/ui/themed-toaster.tsx
- Header hard-coded
rgba
/hex/neon classes: src/components/layout/header.tsx - Sidebar hard-coded
rgba
/hex/neon classes: src/components/layout/sidebar.tsx globals.css
theme vars and global transform overrides: src/app/globals.css
Priority Findings: P0, P1, and P2 Issues
To effectively address the audit findings, we've categorized them based on priority: P0 (highest), P1 (high), and P2 (medium). This prioritization helps us tackle the most critical issues first, ensuring a smooth and consistent user experience. Let's break down each priority level:
P0: Critical Issues Demanding Immediate Attention
These are the most pressing issues that directly impact the user experience and accessibility. Addressing them is our top priority.
1) Remove Inline Transform Suppression:
Inline styles, specifically transform: none
, are overriding Radix animations and interfering with the prefers-reduced-motion
setting. This can lead to inconsistent and potentially jarring animations for users who have specified a preference for reduced motion. We need to remove these inline overrides and rely on CSS for animation control. This ensures that our animations respect user preferences and maintain a consistent experience.
- Button: Remove inline transform override (src/components/ui/button.tsx:49)
- DropdownMenu: Remove inline transform overrides (src/components/ui/dropdown-menu.tsx:34,91,108)
2) Define a Stable Form Background Token:
Currently, there's a gap in our form background token implementation. We're using bg-[hsl(var(--form-bg))]
in our Input and Select components, but --form-bg
isn't defined in our global styles. This inconsistency can lead to unexpected visual results and theming issues. To rectify this, we have two options:
- Option A: Define
--form-bg
inglobals.css
and map it to an existing token (e.g.,var(--background)
). This is a straightforward approach that leverages our existing theming system. - Option B: Replace
bg-[hsl(var(--form-bg))]
with an existing background token likebg-background
,bg-card
, orbg-popover
. This approach eliminates the need for a new token and promotes consistency. - Files: (src/components/ui/input.tsx:15), (src/components/ui/select.tsx:23,48)
P1: High-Priority Normalization Tasks
These issues focus on normalizing our color usage and ensuring consistency across the UI. Addressing these issues will improve the visual coherence and maintainability of our components.
3) Normalize Colors to Tailwind Theme Tokens/CSS Variables:
We need to eliminate hard-coded rgba
/hex color values and transition to using Tailwind theme tokens and CSS variables. This approach provides a centralized and scalable way to manage our color palette, making it easier to maintain consistency and implement theme changes. This will improve the maintainability and scalability of our codebase while enhancing the visual harmony of our application.
- Introduce brand tokens (e.g.,
--brand-primary
,--brand-accent
) within the Tailwind theme. This allows us to define our brand colors in a centralized location and use them consistently throughout the UI. - Refactor the header and sidebar to use tokenized classes (
bg-background
,bg-popover
,border-border
,text-foreground
,muted-foreground
). This ensures that these key layout components adhere to our established color scheme.
4) Tokenize Sonner Toaster Classes:
The Sonner toaster component currently uses raw color classes like text-green-400
, text-red-400
, text-yellow-400
, and text-blue-400
. We need to replace these with semantic tokens that represent the meaning of the colors (e.g., text-success
, text-error
, text-warning
, text-info
). This makes our code more readable and maintainable, as the color usage is tied to the intent rather than the specific color value. This enhances the semantic clarity of our code and facilitates easier maintenance and theming.
P2: Medium-Priority Enhancements
These tasks focus on improving navigation semantics and focus management, further enhancing the accessibility and user experience of our application.
5) Navigation Semantics & Focus:
We should review our navigation implementation to ensure proper semantics and focus management. This includes:
- Considering using the
Button
component with theasChild
prop in conjunction with theLink
component. This allows us to create visually styled buttons that act as links, providing a consistent user experience. - Adding
aria-current
attributes to active routes. This helps assistive technologies understand the user's current location within the application. - Ensuring proper
focus-visible
rings per ShadCN guidelines. This provides clear visual feedback when elements are focused using the keyboard, improving accessibility for keyboard users.
Proposed Minimal Diffs (Phase 1 - Low Risk)
To kick off the normalization process, we're proposing a set of minimal diffs that address the P0 issues and carry a low risk of introducing regressions. These changes are focused on removing inline styles and defining the missing form background token. This phased approach allows us to address critical issues quickly while minimizing the risk of disrupting existing functionality.
- src/components/ui/button.tsx: Remove the inline
style
prop (transform: none
) on the rendered component. - src/components/ui/dropdown-menu.tsx: Remove the inline
style
props (transform: none
) onSubTrigger
,Item
, andCheckboxItem
. - src/app/globals.css: Add
:root --form-bg
referencing an existing token (e.g.,--background
); or update Input/Select to usebg-background
.
Follow-on (Phase 2)
After addressing the P0 issues, we'll move on to Phase 2, which focuses on tokenizing colors and refactoring components to use these tokens. This phase will significantly improve the consistency and maintainability of our UI. These steps will pave the way for a more consistent and themable user interface.
- Introduce design tokens for the neon/brand palette in the Tailwind config and globals using HSL variables. This provides a centralized and scalable way to manage our brand colors.
- Refactor
header.tsx
andsidebar.tsx
to use tokenized classes; remove hard-codedrgba
/hex values. This ensures that these key layout components adhere to our established color scheme. - Tokenize Sonner UI classes and icons. This enhances the semantic clarity of our code and facilitates easier maintenance and theming.
Acceptance Criteria
To ensure the success of our normalization efforts, we've defined clear acceptance criteria:
- Motion respects
prefers-reduced-motion
; Radix animations are unaffected by inline overrides. This ensures that our animations are accessible and respect user preferences. - Inputs and selects render consistently via defined tokens across themes. This guarantees a consistent visual experience across different themes.
- Header and sidebar adhere to tokenized colors with improved contrast and accessibility. This enhances the visual harmony and accessibility of our application.
Testing & Verification
We'll be employing a comprehensive testing and verification strategy to ensure the quality of our changes:
- Visual snapshots for Button, Dropdown, Input, Select, Header, and Sidebar (using Chromatic/Percy). This allows us to detect any visual regressions introduced by our changes.
- Accessibility checks (axe) and keyboard navigation testing for nav and dialogs. This ensures that our UI is accessible to all users.
- E2E smoke tests on login and settings navigation. This verifies that key functionalities of our application are working as expected.
Rollout Plan
We'll be rolling out the changes in a series of pull requests (PRs) to minimize risk and facilitate code review:
- PR1: Minimal P0 diffs (transform overrides removal, form-bg token or component background swap). This addresses the most critical issues with a low-risk approach.
- PR2: Token introduction and header/sidebar normalization. This implements the core color tokenization and refactors key layout components.
- PR3: Sonner tokenization and nav semantics. This completes the color tokenization efforts and improves navigation accessibility.
Notes
The good news is that our ShadCN baseline is solid! The primary focus of this effort is normalization and token discipline rather than component replacement. This means we're building upon a strong foundation and refining our implementation for long-term maintainability and scalability. This approach allows us to leverage the strengths of ShadCN while ensuring a consistent and accessible user interface.