Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Pigment CSS v1: Package Structure and API Design #362

Open
brijeshb42 opened this issue Jan 6, 2025 · 5 comments
Open

[RFC] Pigment CSS v1: Package Structure and API Design #362

brijeshb42 opened this issue Jan 6, 2025 · 5 comments
Assignees
Labels
RFC Request For Comments status: waiting for maintainer These issues haven't been looked at yet by a maintainer

Comments

@brijeshb42
Copy link
Contributor

brijeshb42 commented Jan 6, 2025

What's the problem?

  1. The current Pigment CSS implementation is tightly coupled with Material-UI specific paradigms:

    • Contains styleOverrides and components handling for Material UI's theme
    • Includes slot functionality that's not core to CSS-in-JS
    • Theme handling is focused on Material-UI patterns rather than generic CSS tokens
    • When it was implemented, it was highly focussed on its usage with Material UI v6.
  2. The current architecture makes it difficult to:

    • Use Pigment CSS independently without linking it to Material UI's concepts
    • The variant support is not the based since it's the equivalent of compound variants in Stitches and we also don't support single variants.
    • Maintain clean separation between core CSS-in-JS functionality and UI framework specifics

What are the requirements?

  1. Core Functionality:

    • Framework-agnostic CSS-in-JS implementation
    • Build-time CSS extraction and optimization
    • Support for CSS token management and theming
    • Clear separation between core and framework-specific features
  2. Developer Experience:

    • Intuitive API design combining best aspects of Emotion and Stitches
    • Type-safe styling and theming
    • Easy integration with popular build tools
    • Support for variant-based styling
  3. Performance:

    • Efficient runtime performance through build-time optimization
    • Minimal runtime overhead for theme customization
    • Proper CSS layering for style precedence

What are our options?

Option 1: Enhance Current Architecture

  • Pros:
    • Minimal migration needed
    • Maintains compatibility with existing code
  • Cons:
    • Perpetuates tight coupling with Material-UI
    • Limits adoption by non-Material-UI users
    • Complicates maintenance

Option 2: Complete Rewrite

  • Pros:
    • Clean slate for better architecture
    • No legacy constraints
  • Cons:
    • High migration cost
    • Potential breaking changes
    • Longer development time

Option 3: Modular Refactor (Proposed)

  • Pros:
    • Clean separation of concerns
    • Maintains compatibility through adapter packages
    • Enables broader adoption
  • Cons:
    • Moderate migration effort
    • Additional package maintenance

Proposed solution

1. Package Structure

@pigment-css/
├── core/          # Framework-agnostic CSS-in-JS
├── theme/         # Theme token management
├── react/         # React-specific primitives
├── processors/    # Build-time processors
└── bundler-plugins/
    ├── vite/
    ├── webpack/
    └── nextjs/

2. Core APIs

Theme Management

// Theme definition
interface Theme {
  palette: {
    primary: { main: string },
    secondary: { main: string }
  },
  spacing: Record<string, number>
}

// Runtime theme customization
const themeStyles = createTheme({
  palette: {
    primary: { main: 'blue' }
  }
});

Styling

// Base styling with variants
const button = css({
  // goes into pigment.base layer
  padding: "6px 16px",
  variants: {
    // goes into pigment.variants layer
    size: {
      small: { padding: "4px 8px" },
      large: { padding: "8px 24px" }
    }
  },
  // goes into pigment.compoundvariants layer
  compoundVariants: [{
    size: 'small',
    color: 'primary',
    css: {
      padding: 4
    }
  }],
  defaultVariants: {
    size: "medium"
  }
});

// React components
const Button = styled("button")({
  minWidth: 64,
  variants: {
    variant: {
      contained: { /* styles */ },
      outlined: { /* styles */ }
    }
  }
});

Digressing for the currently released version

  1. theme -> In current version or the one supporting Material UI, the theme object, besides the tokens itself contains a lot of configuration, like sxConfig, utils like theme.breakpoints.up/down, component styling like theme.components, component's default props through theme.props.
    In v1, theme will purely be a nested collection of tokens and will mostly involve primitive values like string or number. You can still add methods to the theme but they won't be part of the final runtime. You can only use those methods in your css definition (same as theme.breakpoints.up). They won't carry any extra meaning for any of the components.
  2. Generated css (through css or styled calls will be part of css layers that'll decide the precedence so that it is easier to override base styles of derived styled components. This does not happen in the current release.
  3. Better variants supports through Stitches like API instead of just allowing variants array (similar to compound variants).

3. Bundler Integration

Mostly same as the current configuration with more options to customize the output css or class names, like

const pigmentConfig = {
  theme: tokenObject,
  sxConfig: {
    // to use `size` as css({size: 4}) outputs .classname {width: 4px; height: 4px;}
    size(value) {
      return {
        width: value,
        height: value,
      }
    },
    // to use `mobile` as css({mobile: { padding: 4 }}) outputs
    // @media only screen and (max-device-width: 480px) {.classname { padding: 4px } }
    mobile(cssObj) {
      return {
        '@media only screen and (max-device-width: 480px)': {
           // css for mobile devices
        },
      };
    },
  }

This will also be through typescript to get suggestions in the code.

4. CSS Output Structure

@layer pigment.tokens {
  /* Theme tokens as CSS variables */
}
@layer pigment.base {
  /* Component base styles */
}
@layer pigment.variants {
  /* Variant styles */
}
@layer pigment.compoundvariants {
  /* Variant styles */
}

The layers will help users to easily override the base or variant styles of derived styled components like -

const OriginalComponent = styled.button({
  color: 'red',
  variants: {
    primary: {
      color: 'blue',
    },
  },
});

const OverriddenComponent = styled(OriginalComponent)({
  color: 'yellow',
  variants: {
    primary: {
      color: 'green',
    },
  },
});

This can be easily achieved with layers in context of build time css extraction.

Output CSS

@layer pigment.base {

  .OriginalComponent {
    color: red;
  }

  .OverriddenComponent {
    color: yellow;
  }

}
@layer pigment.variants {

  .OriginalComponent-variant-primary-color {
    color: blue;
  }

  .OverriddenComponent-variant-primary-color {
    color: green;
  }

}

Migration Strategy

  1. Release core packages with stable API
  2. Provide Material-UI adapter package
  3. Support gradual migration through compatibility layer
  4. Document migration patterns and examples

Resources and benchmarks

https://github.com/mui/pigment-css/tree/master/packages/pigment-css-theme

#345
Core Package
React package

@brijeshb42 brijeshb42 added status: waiting for maintainer These issues haven't been looked at yet by a maintainer RFC Request For Comments labels Jan 6, 2025
@brijeshb42 brijeshb42 self-assigned this Jan 6, 2025
@brijeshb42 brijeshb42 pinned this issue Jan 9, 2025
@astrodomas
Copy link

astrodomas commented Jan 24, 2025

Hey, could you emphasize more on how will this work with nextjs dynamic api, will CSS be granular in respect to JS code splitting?

@joslarson
Copy link

joslarson commented Feb 1, 2025

As part of the move to the Stitches-like API for variants, do you have plans to support responsive variants? It could still be static, for example:

const Button = styled.button({
  variants: {
    size: {
      small: {padding: '4px'},
      medium: {padding: '6px'},
      large: {padding: '8px'},
    },
  },
  defaultVariants: {size: 'medium'},
});

const ResponsiveButton = styled(Button)({
  defaultVariants: {
    size: {
      '@initial': 'small',
      '@media (min-width: 1024px)': 'medium',
      '@media (min-width: 1400px)': 'large',
    },
  },
})

Output CSS

@layer pigment.base {
  .Button {}

  .ResponsiveButton {}
}

@layer pigment.variants {
  .Button-variant-size-small {
    padding: 4px;
  }

  .Button-variant-size-medium {
    padding: 8px;
  }

  .Button-variant-size-large {
    padding: 16px;
  }

  .ResponsiveButton-responsive-variant-size {
    @media (min-width: 1024px) {
      padding: 8px;
    }

    @media (min-width: 1400px) {
      padding: 8px;
    }
  }
}

@brijeshb42
Copy link
Contributor Author

This particular aspect hasn't been considered yet but seems like a nice addition. We can re-visit this after the release of first version since we don't want to delay the first release.
Adding it to the next milestone.

@brijeshb42 brijeshb42 added this to the Post V1 changes milestone Feb 1, 2025
@joslarson
Copy link

I'll also mention here that this pattern has a future of first class support in css, removing the need for rule duplication, once style queries gains wide browser support.

@layer pigment.base {
  .Button {
    @container style(--Button-variant-size: small) {
      padding: 4px;
    }
    @container style(--Button-variant-size: medium) {
      padding: 6px;
    }
    @container style(--Button-variant-size: large) {
      padding: 8px;
    }
  }

  .ResponsiveButton {}
}

@layer pigment.variants {
  .Button-variant-size-small {
    --Button-variant-size: small;
  }

  .Button-variant-size-medium {
    --Button-variant-size: medium;
  }

  .Button-variant-size-large {
    --Button-variant-size: large;
  }

  .ResponsiveButton-responsive-variant-size {
    @media (min-width: 1024px) {
      --Button-variant-size: medium;
    }

    @media (min-width: 1400px) {
      --Button-variant-size: large;
    }
  }
}

@keegan-lillo
Copy link

Apologies for the naive question, but is this RFC what we can expect the new API to look like? Specifically with regards to the styled API? I'm a big fan of the Stitches-like variants API, particularly with the ability to specify default variants as well as infer TypeScript types from the declaration without needing to specify them separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
RFC Request For Comments status: waiting for maintainer These issues haven't been looked at yet by a maintainer
Projects
None yet
Development

No branches or pull requests

4 participants