Skip to content

Conversation

@dmytrokirpa
Copy link
Contributor

Previous Behavior

New Behavior

Related Issue(s)

  • Fixes #

@github-actions
Copy link

github-actions bot commented Nov 6, 2025

Pull request demo site: URL

@dmytrokirpa dmytrokirpa force-pushed the experiment/unstyled_css branch from f81e722 to f2b8caf Compare November 6, 2025 13:30
@dmytrokirpa dmytrokirpa force-pushed the experiment/unstyled_css branch from f2b8caf to 2f51180 Compare November 7, 2025 15:21
@github-actions github-actions bot added Type: RFC Request for Feedback and removed NX: core labels Nov 7, 2025
@dmytrokirpa dmytrokirpa changed the title [DO NOT MERGE]: Experiment/unstyled css RFC: Unstyled/headless components Nov 7, 2025
@github-actions
Copy link

github-actions bot commented Nov 7, 2025

📊 Bundle size report

✅ No changes found

- ✅ Component files unchanged (still supports `useCustomStyleHook_unstable`)
-**~25% JS bundle size reduction** (tested) by excluding Griffel runtime

**Note:** To completely eliminate Griffel from an application, unstyled variants are needed for **all components that use Griffel**, including infrastructure components like `FluentProvider`. This ensures no Griffel runtime is bundled.
Copy link
Contributor

Choose a reason for hiding this comment

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


export const useButtonStyles_unstable = (state: ButtonState) => {
// Only apply base class names, no styles
state.root.className = mergeClasses(buttonClassNames.root, state.root.className);
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like something that is missing here is that the style hooks are implicitly adding "state classes" when they apply styles based on component state (example).

I think we'll need to introduce well-known classes for these so users bringing their own styles can correctly respond to state changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just had another idea — not directly tied to this proposal, but it could help with the whole well-known classes issue. What if we exposed component state as data-attributes on DOM elements using the useComponentState hook? For example, we could set data-appearance="outline|underline|etc" and data-size="small|large|etc". That way, we could easily use these in styles, tests, and more. It also can eliminate some JS (runtime classes mapping)

Here are a couple of examples from other DS/UI libraries doing something similar:


Unstyled variants are opt-in via bundler extension resolution (similar to [raw modules](https://storybooks.fluentui.dev/react/?path=/docs/concepts-developer-unprocessed-styles--docs#how-to-use-raw-modules), ensuring zero breaking changes.

**Performance Impact:** Internal testing shows **~25% JavaScript bundle size reduction** when using unstyled variants, as Griffel runtime and style implementations are excluded from the bundle.
Copy link
Member

Choose a reason for hiding this comment

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

What will be an increase once you will have actual CSS that matches what we have currently? Nobody is going to use components without any styles.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It mainly depends on how much styles consumers want to use. Even if the number (25%) isn't completely accurate, users will still need to pay for any default styles they don't intend to use.

I tried to check the increase with the actual CSS version this by switching the style hook to CSS modules, but our tooling (monosize) only takes into account JS, not CSS. It's possible I missed a configuration or setting.

Copy link
Member

@layershifter layershifter Nov 18, 2025

Choose a reason for hiding this comment

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

I tried to check the increase with the actual CSS version this by switching the style hook to CSS modules, but our tooling (monosize) only takes into account JS, not CSS. It's possible I missed a configuration or setting.

It indeed won't measure CSS by default.

IMO it's crucial to provide measurements to compare apples to apples. As currently, "25% reduction" is a false message.

Copy link
Contributor Author

@dmytrokirpa dmytrokirpa Nov 18, 2025

Choose a reason for hiding this comment

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

I wouldn't call it a "false" message, as it clearly says "JavaScript bundle size".

Here is where I got data for comparisons:

  • Griffel + AOT + NO CSS extraction
image
  • Griffel + AOT + CSS extraction
image
  • "Unstyled" - No Griffel or default styles in JS, styles are in external CSS
image

Copy link
Member

Choose a reason for hiding this comment

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

I got what you mean, however you won't use solely JavaScript, correct? 🐱 Otherwise cards would look like that:

image

P.S. In the Griffel scenario we will need have part of Griffel runtime + mappings for merging which contributes to JS size.


I've ran "Griffel + AOT + CSS extraction" scenario for Card.fixture.js:

  • JS 82.432 kB / 25.161 kB
  • CSS 13K / 2.52 kB
  • Total: 95.4 K / 27.6 K

Can you please provide the same for "Without Griffel - "unstyled" + plain CSS"? (considering that CSS file for Card should match current styles)

From the RFC, I noticed that the initial plan is to update 10 components, so it would good to have the same for all them to be realistic.

Copy link
Contributor

Choose a reason for hiding this comment

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

we should probably provide a realistic sample implementation with different styles and use that for comparison. and make sure we measure it

Copy link
Contributor Author

@dmytrokirpa dmytrokirpa Nov 18, 2025

Choose a reason for hiding this comment

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

we should probably provide a realistic sample implementation with different styles and use that for comparison. and make sure we measure it

Totally agree! Just to clarify, reducing bundle size wasn’t our only reason for this. The main goal is to support partners who have their own unique UI needs - like, their designs don’t use Fluent 2, they have specific tech/performance requirements, and they aren’t using Griffel or default Fluent styles. They’d rather not include stuff they don’t actually need.

**Pros:** Single source of truth, automatic
**Cons:** Complex build config, harder to debug

## Usage Examples
Copy link
Contributor

Choose a reason for hiding this comment

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

can you pls also include examples for styles applied conditionally based on state and/or props? for example how to style a toggle button with different background based on toggle state or how to style a secondary button?

Copy link
Contributor Author

@dmytrokirpa dmytrokirpa Nov 18, 2025

Choose a reason for hiding this comment

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: RFC Request for Feedback

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants