Skip to content
This repository was archived by the owner on Mar 25, 2026. It is now read-only.
This repository was archived by the owner on Mar 25, 2026. It is now read-only.

v2.0: Make all components truly headless (remove ALL default styles) #97

@ryanrozich

Description

@ryanrozich

Summary

All components in this library should be truly headless with ZERO default styles. Currently, several components ship with CSS, violating the headless architecture principle.

Problem

  1. Components have hardcoded styles that users cannot easily override
  2. Dark mode support is inconsistent (some use media queries, some use classes)
  3. Users must use \!important to override default styles
  4. Bundle size is increased unnecessarily with CSS that many users won't want

Components That Need CSS Removed

1. QuickFilterDropdown

  • File to delete: src/components/QuickFilterDropdown/QuickFilterDropdown.module.css (643 lines!)
  • Changes needed: Remove all styles imports and className applications

2. DateFilter/RelativeDateFilter

  • Styles to remove from: src/index.css
    • .ag-grid-date-filter .toggle-button
    • .ag-grid-date-filter .toggle-button.active
    • .ag-grid-date-filter .toggle-button.inactive
    • .ag-grid-date-filter .relative-date-input
    • .ag-grid-date-filter .resolved-date
    • .ag-grid-date-filter .input-wrapper
    • .ag-grid-date-filter .filter-buttons
    • .ag-grid-date-filter .filter-button
    • .ag-grid-date-filter .apply-button
    • .ag-grid-date-filter .reset-button
    • .ag-grid-date-filter .error-message

3. Any other components with CSS

  • Audit ALL components for any CSS imports or style applications

Proposed API Changes

Option 1: className props for each element

<QuickFilterDropdown
  classNames={{
    container: 'my-container',
    trigger: 'my-trigger-button',
    dropdown: 'my-dropdown-panel',
    option: 'my-option',
    // etc...
  }}
/>

Option 2: Render props for full control

<QuickFilterDropdown
  renderTrigger={({ isOpen, selectedOption }) => (
    <button className="my-custom-button">
      {selectedOption?.label || 'Select...'}
    </button>
  )}
  renderOption={({ option, isSelected, isHighlighted }) => (
    <div className={cn('my-option', { selected: isSelected })}>
      {option.label}
    </div>
  )}
/>

Option 3: Unstyled primitives

// Export unstyled building blocks
import { 
  FilterDropdown,
  FilterTrigger,
  FilterOption 
} from 'ag-grid-react-components/primitives'

Migration Guide Required

  • Document how to migrate from v1 to v2
  • Provide example styles that match current defaults
  • Show Tailwind, CSS modules, and styled-components examples

Benefits

  1. True headless architecture
  2. Smaller bundle size (no CSS)
  3. Full styling control for users
  4. Consistent with modern headless libraries (Headless UI, Radix, etc.)
  5. No more CSS specificity battles

Breaking Change

This is a MAJOR breaking change and requires v2.0.0 release.

Implementation Plan

  1. Remove all CSS files and imports
  2. Update components to accept className/classNames props
  3. Move all current styles to demo as examples
  4. Update documentation with styling examples
  5. Create migration guide
  6. Update CLAUDE.md with strict "NO CSS" policy

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Projects

    Status

    ✅ Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions