# Future of theming blog post We should be thinking about how we want to communicate any big changes in various documentation channels. Theming/styling will be at least one big blog post, and the story should be clear about how users will move forward. Perf ---- Performance improvement is one of the big goals in Fabric 8. ### Styles as a function of props Many css-in-js libraries allow you to make the css dynamic as a function of props. In practice, this leads to significant performance penalties. Consider this: ``` <Button styles={ p => { root: p.disabled ? p.disabledBackgroundColor : p.backgroundColor } } /> ``` Perf penalty 1: Everytime the Button renders, the styles function needs to be evaluated. Perf penalty 2: After resolution, the styles are passed to the styling system, which needs to generate a hash of the object to identify if the rule has been registered. Perf penalty 3: If the hash is missing for a given style object, the rule must be injected into the system. We've tried to improve perf in Fabric 7 by building a graph of props values that equal the result of classnames. This means rendering 1000 buttons with the same props would evaluate the classnames 1 time, but it is far from perfect and super abusable: 1. Things which mutate constantly create unlimited classnames. 2. The graph is built from our "style props" which is a subset of user props. We assume the style props are ALWAYS provided, in the same order. But object props, while seemingly always in the same order, are not guaranteed to iterate the same order when read. #### What this means in Fabric 8: Currently in Fabric 7, we support `IStyleFunctionOrObject` which represents a style function ```jsx p => { part: { ...rules } } ``` or an object ```jsx { part: { ...rules } } ``` We are proposing two major changes: 1. The `props` of a component will never be used as an input to styling. Instead, the style set will include classnames which are injected. 2. The only parameter passed into the styles function will be the theme. The theme does not change from component to component, so it makes a great cache key. If the theme does change we can invalidate the entire stylesheet and build it again. ### Inline styled tokens going away Style tokens make it hard to imprint CSS on the page once and exactly once. Forcing token use through the `styled` or `compose` helpers makes it possible to guarantee better performance. ```jsx /* NO! */ // requires styles, makes perf optimizations very difficult <Button styles={{ background: 'red' }} /> /* YES! */ const RedButton = styled( Button, { tokens: { 'boxFillChecked': 'red' } } ); ``` Goals ===== ```jsx <HEAD> <style data-type="Button">...</style> <style data-type="ButtonInline">...</style> </HEAD> const redStyles = { root: { background: 'red'}, rootDisabled: theme => {}, container: { '$rootDisabled &': {} }, }; <Button styles={{ root: { background: ... } }} /> const RedButton = composed(Button, { name: 'RedButton', tokens: { background: 'red' }, default: { overrides: { hovered: {}, } } }); ``` ## Styling does not depend on props; only on theme and tokens. All permutations of classes are generated ## Tokens In order to make the theming/styling story a marked improvement on the existing system, there are several goals: Creating a base component does not have a dependency on the styling system -------------------------------------------------------------------------- i.e. a themeless base package exists, not influcenced by a design team Creating a styled component DOES add a dependency on a styling system --------------------------------------------------------------------- A developer should only need to add 1 package to a `package.json` in order to start using Fabric components. Focus on composability ---------------------- Where theming/styling is a function of composition. ### P1 - Tokens should allow a 1-off that allows user to override a specific aspect ``` jsx const RedButton = styled(Button, { tokens: { 'boxFillChecked': 'red' } }); ``` `RedButton` is an example - it\'s easy to create and re-use. ### P1 - Universally replace a component with a new version of a component For example: I have a `FastIcon` and I want to replace **all** instances of `Icon` with my new `FastIcon`. How do I do this? ### P2 - Slots/Themes - should allow a theme to specify a new component for a slot For example: An app might want to flight a change where a button use a `FastIcon` for a specific set of users or inside a specific region of the view. # Components There are a few dicsussions/proposals in flight at the moment ## Styled helper, composed helper Currently a PR open by Mak for `compose`. ### Styled helper manages library-specific logic with recomposibility in mind ### Composed manages slots, slotProps, state, and view with recomposibility in mind ### Pros 1. Can recompose and generate new typings at the same time. ### Cons Styled helper, JSX slots w/ prop parsing helper ----------------------------------------------- Currently a code sandbox (+ forks) by David. ### Styled helper manages library-specific logic with recomposibility in mind ### JSX slots w/ prop parsing helper ### Pros 1. View, state, and slotProps are in 1 function, easy to understand ### Cons 1. Recomposing view or state requires redefining a function (potentially nested in a library) 2. Providing slot overrides does not modify typings. Next Steps ========== In order to decide on an API we can use moving forward, we need to rapidly decide on criteria we will use to make a decision and implement that decision. We should take the existing `composed` PR that Mak has open, add the ability to mix in theming, then proceed to build a button component using: - a theme-agnostic base button - a msft \"design system\" implementation - a msft design system aware button Once this button exists, we will compare ergonomics and perf against the currently shipping button and the experimental button and figure out the path foward. # Example of ideal implementation ```jsx // In @uifabric/react-base import { composed } from '@uifabric/react-composed' const BaseButton = composed({ name, state, view, slots, slotProps }); // In react-composed-jss import { composed } from '@uifabric/react-msft-composed'; import jss from 'jss'; export const composedJss = composed.extend({ /* compose options to extend it to support style/token/theme solution */ process: () => {} }) // in @uifabric/react-msft-jss import { composed } from '@uifabric/react-composed-jss'; import { designSystem } from '@uifabric/msft-design-system'; export const ButtonMsft = composed(BaseButton, { styles: { root: { '$foo &': {} }, foo: {}, }, tokens: {} }); // in @uifabric/react-msft-css // import { composed } from import { composed } from '@uifabric/react-composed-css'; import { designSystem } from '@uifabric/msft-design-system'; import styles from 'Button.scss'; export const ButtonMsft = composed(BaseButton, { classNames: styles, tokens: {} }); ```