{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreifes2ldcbnzoceoynuqhsqvsdde7obly625rcm5bfnumfcwafnwpq",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mpt7xfrnt622"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreia4reuyhpnqskuv37spz36s7bxe4cthcsd3be5iazbl52cjnstnpi"
    },
    "mimeType": "image/webp",
    "size": 279188
  },
  "path": "/omnidev/bringing-react-native-style-composition-to-react-d1l",
  "publishedAt": "2026-07-04T13:27:46.000Z",
  "site": "https://dev.to",
  "tags": [
    "react",
    "reactnative",
    "typescript",
    "css",
    "https://github.com/fluentic-dev/style",
    "https://fluenticstack.com/style",
    "@fluentic"
  ],
  "textContent": "> Fluentic is my attempt to make React styling feel explicit, composable, and predictable again.\n\nYears ago, I worked on web apps with Emotion, and I loved it.\n\nIt was flexible, productive, and convenient. But over time, some of the codebases I worked on became harder and harder to change. I do not blame Emotion for that. The problem was how I used the convenience it gave me.\n\nI wrote class names that depended on DOM structure. I used nested selectors because they were quick. I reached into child elements from parent styles. It worked, until months later I had to change something and no longer felt confident about what else would break.\n\nThat feeling is what I remember most.\n\nNot that the styling was impossible. Not that the tool was bad. Just that I slowly lost confidence when changing UI.\n\n##  The Problem Was Confidence\n\nA lot of frontend styling problems are not about whether something is technically possible.\n\nCSS is powerful. CSS-in-JS is powerful. Utility classes are powerful. CSS Modules are useful. Static extraction is useful.\n\nThe hard part is usually confidence.\n\nCan I change this component without accidentally changing another component?\n\nCan I understand where this style comes from?\n\nCan I tell which style wins?\n\nCan I move this element without breaking some selector far away?\n\nCan I delete this class name?\n\nCan I add a variant without making the component more fragile?\n\nWhen styles depend on class names, DOM structure, nested selectors, and implicit relationships between elements, every change becomes a little less obvious.\n\nYou can still build good systems this way. Many teams do.\n\nBut the styling model makes it very easy to create hidden coupling.\n\nAnd hidden coupling is where confidence starts to fade.\n\n##  Then I Spent Years in React Native\n\nAfter that, I spent years mostly working on React Native projects.\n\nReact Native styling changed how I think about UI.\n\nThe styling model is much smaller:\n\n\n\n    <View style={[styles.root, disabled && styles.disabled, style]} />\n\n\nStyles are objects.\n\nStyles are passed as values.\n\nArrays compose styles.\n\nThe later style wins.\n\nVariants are usually explicit.\n\nThe style belongs close to the element that uses it.\n\nThere is no class name pretending to be both identity and styling hook. There is no global selector reaching through some DOM structure. There is much less “where did this come from?”\n\nOf course React Native styling is not perfect. It has its own limitations. But the mental model is very simple.\n\nAnd after using that model for years, I came back to React web.\n\nThat was when web styling felt more complicated to me than it used to.\n\nNot because CSS got worse.\n\nActually, CSS got much better.\n\nWe have better layout. Better selectors. Better browser support. Better build tools. Better frameworks.\n\nBut for component-heavy React work, I started to miss the React Native mental model a lot.\n\nI rarely see React web authors style things like this:\n\n\n\n    <Component style={[base, primary && primaryStyle, props.style]} />\n\n\nBut in React Native, this is completely normal.\n\nAnd that normality matters.\n\nIt makes styling feel like data. Like values. Like composition.\n\nNot like a string contract with the DOM.\n\n##  React Web Has Something React Native Misses\n\nBut I do not want to pretend React Native has the perfect model.\n\nIt does not.\n\nThe web has something extremely useful: selectors.\n\nSometimes selectors are exactly what you want.\n\nYou want hover states:\n\n\n\n    .button:hover {\n      background: var(--hover);\n    }\n\n\nYou want focus states.\n\nYou want child coordination.\n\nYou want media queries.\n\nYou want pseudo elements.\n\nYou want to style based on parent state.\n\nYou want to express relationships without manually passing a style prop into every single element.\n\nThat is real power.\n\nReact Native often makes this more manual. You wire state. You pass style props. You conditionally apply styles element by element.\n\nThat explicitness is nice until it becomes repetitive.\n\nSo the question I kept thinking about was:\n\nCan React styling keep the simple React Native mental model, but still keep the useful parts of CSS?\n\nCan styles be values, while selectors still exist?\n\nCan composition be explicit, while output is still real CSS?\n\nCan override order be obvious, while runtime work stays small?\n\nThat question became the beginning of Fluentic.\n\n##  Extraction Is Not the New Idea\n\nModern React styling has changed a lot.\n\nRuntime CSS-in-JS used to feel like a reasonable default for many React apps. Today, the tradeoffs are more visible.\n\nPerformance matters.\n\nServer rendering matters.\n\nStreaming matters.\n\nReact Server Components changed what can happen on the server and what should happen on the client.\n\nBuild-time extraction is no longer a niche idea. In many modern stacks, it feels close to mandatory.\n\nBut extraction itself is not the interesting part anymore.\n\nThere are already many libraries that extract CSS.\n\nThat is not the main reason I started working on Fluentic.\n\nThe thing I felt missing was not “another extracted CSS-in-JS library”.\n\nThe thing I wanted was a different authoring model.\n\nA model closer to what I had in my head after years of React Native:\n\n  * styles as values\n  * explicit composition\n  * predictable override order\n  * local ownership\n  * variants without magic class names\n  * selectors when useful, but not as the default way to connect everything\n  * build-time output that fits modern React apps\n\n\n\nThat is the shape of Fluentic.\n\n##  What I Want From a Styling System\n\nWhen I started thinking about Fluentic, I wrote down the requirements more like a frontend developer who has been hurt by his own past decisions than like someone trying to invent a styling library.\n\nThe requirements were not only technical.\n\nThey were emotional too.\n\nI wanted to feel confident changing UI again.\n\n###  1. Styles Should Be Values\n\nIn React Native, a style is something you can pass around.\n\n\n\n    <View style={styles.card} />\n\n\nOr compose:\n\n\n\n    <View style={[styles.card, elevated && styles.elevated]} />\n\n\nThat feels very different from:\n\n\n\n    <div className={`${styles.card} ${elevated ? styles.elevated : \"\"}`} />\n\n\nThe class name version works, of course. But the mental model is different.\n\nOne feels like composing values.\n\nThe other feels like constructing a string that points to styling somewhere else.\n\nIn Fluentic, the equivalent idea uses `style(...)` and the JSX `css` prop:\n\n\n\n    import { style } from '@fluentic/style';\n\n    const styles = {\n      card: style({\n        padding: 16,\n        borderRadius: 8,\n        backgroundColor: 'white',\n      }),\n\n      elevated: style({\n        boxShadow: '0 12px 30px rgb(15 23 42 / 0.16)',\n      }),\n    };\n\n    function Card({ elevated }: { elevated?: boolean }) {\n      return (\n        <section css={[styles.card, elevated && styles.elevated]}>\n          Ready to style.\n        </section>\n      );\n    }\n\n\nThe syntax is different from React Native, because the target is still the browser.\n\nBut the feeling is familiar:\n\nBase style first.\n\nConditional style after.\n\nThe style value is passed directly to the element.\n\n###  2. Override Order Should Be Obvious\n\nOne of the nicest things about React Native style arrays is that the later item wins.\n\n\n\n    <View style={[styles.base, styles.large, props.style]} />\n\n\nYou can read that line and understand the override order.\n\nBase first.\n\nThen large.\n\nThen external style.\n\nThere is very little mystery.\n\nOn the web, override order can come from many places:\n\n  * stylesheet order\n  * selector specificity\n  * class order\n  * nested selectors\n  * cascade layers\n  * inline styles\n  * generated CSS order\n\n\n\nSome of these are powerful. Some are necessary. But when building components, I often want the boring rule:\n\n> later style wins\n\nFluentic is designed around that kind of predictability.\n\n\n\n    <button css={[styles.button, primary && styles.primary, props.css]}>\n      Save\n    </button>\n\n\nThat is the kind of composition I want to keep visible in React code.\n\n###  3. Variants Should Be Explicit\n\nI have written many components where styles were controlled by a mix of class names and nested selectors.\n\nSomething like:\n\n\n\n    .card.compact .title {\n      font-size: 14px;\n    }\n\n    .card.highlighted .title {\n      color: blue;\n    }\n\n\nThis is convenient.\n\nIt also couples the parent state, child structure, and class names together.\n\nLater, when the component changes, the style assumptions are still sitting there.\n\nFor a local component, explicit composition can be enough:\n\n\n\n    const styles = {\n      card: style({\n        padding: 16,\n        borderRadius: 8,\n        backgroundColor: 'white',\n      }),\n\n      compactCard: style({\n        padding: 12,\n      }),\n\n      title: style({\n        fontSize: 20,\n        fontWeight: 700,\n      }),\n\n      compactTitle: style({\n        fontSize: 16,\n      }),\n    };\n\n    function Card({ compact }: { compact?: boolean }) {\n      return (\n        <div css={[styles.card, compact && styles.compactCard]}>\n          <h2 css={[styles.title, compact && styles.compactTitle]}>\n            Title\n          </h2>\n        </div>\n      );\n    }\n\n\nThis may look less magical.\n\nThat is the point.\n\nA little less magic often means a lot more confidence later.\n\n###  4. Reusable Components Need More Than Root Styling\n\nRoot-level styling is only the small case.\n\nReusable React components often have internal parts.\n\nA button may have a root, an icon, and a label.\n\nA card may have a root, header, title, content, and footer.\n\nA caller might need to customize those parts without depending on DOM structure.\n\nWith normal class names, the public styling API often becomes accidental:\n\n\n\n    <button className=\"button\">\n      <span className=\"button__icon\" />\n      <span className=\"button__label\">Save</span>\n    </button>\n\n\nThat works.\n\nBut now the caller knows your class names.\n\nAnd if they use selectors, they may also start to know your markup.\n\nFluentic uses slots for this.\n\nA slot is a named styling target that a component chooses to expose:\n\n\n\n    import { style } from '@fluentic/style';\n\n    const buttonStyles = {\n      root: style.slot({\n        display: 'inline-flex',\n        alignItems: 'center',\n        gap: 8,\n        border: 0,\n        borderRadius: 8,\n        padding: '8px 12px',\n      }),\n\n      icon: style.slot({\n        display: 'inline-flex',\n        width: 16,\n        height: 16,\n      }),\n\n      label: style.slot({\n        fontWeight: 650,\n      }),\n    };\n\n\nThis says something important:\n\n`root`, `icon`, and `label` are allowed styling targets.\n\nThe DOM can stay private.\n\nThe component decides what it exposes.\n\n###  5. Selectors Should Exist, But They Should Not Become the Whole Model\n\nI still want hover and focus styles to feel natural.\n\nIn Fluentic, a simple element style can use fluent selector/state chains:\n\n\n\n    const button = style({\n      backgroundColor: 'white',\n      border: '1px solid var(--color-border)',\n    }).hover({\n      backgroundColor: 'var(--color-surface-hover)',\n    }).focusVisible({\n      outline: '2px solid var(--color-focus)',\n      outlineOffset: 2,\n    });\n\n\nHover and focus states belong naturally in CSS.\n\nI do not want to manually wire every interaction state in JavaScript just to avoid selectors.\n\nBut I also do not want selectors to become the main way components talk to each other.\n\nSelectors are useful.\n\nSelectors are also where hidden relationships often begin.\n\nSo Fluentic should support selectors, but still encourage local, explicit style ownership.\n\n###  6. Runtime Work Should Be Small\n\nModern React apps already have enough work to do at runtime.\n\nStyling should not require unnecessary client-side computation when the result can be known at build time.\n\nThis matters even more with SSR and React Server Components.\n\nSo Fluentic needs extraction.\n\nBut again, extraction is the implementation requirement, not the whole product idea.\n\nThe product idea is the authoring model.\n\n##  What Fluentic Is Trying To Be\n\nFluentic is my attempt to bring a React Native-inspired styling mental model to React web apps.\n\nThe goal is not to hide CSS.\n\nThe web is still the web.\n\nYou still need to understand layout, cascade, inheritance, media queries, pseudo states, and browser behavior.\n\nFluentic is not trying to make CSS disappear.\n\nIt is trying to make component styling feel more explicit, composable, and predictable.\n\nThe smallest version looks like this:\n\n\n\n    import { style, type StyleProp } from '@fluentic/style';\n\n    const styles = {\n      button: style({\n        height: 36,\n        paddingInline: 12,\n        borderRadius: 6,\n        border: '1px solid var(--color-border)',\n        backgroundColor: 'var(--color-surface)',\n      }).hover({\n        backgroundColor: 'var(--color-surface-hover)',\n      }),\n\n      primary: style({\n        borderColor: 'var(--color-accent)',\n        backgroundColor: 'var(--color-accent)',\n        color: 'white',\n      }),\n\n      disabled: style({\n        opacity: 0.5,\n        pointerEvents: 'none',\n      }),\n    };\n\n    type ToolbarButtonProps = React.ComponentProps<'button'> & {\n      primary?: boolean;\n      css?: StyleProp;\n    };\n\n    function ToolbarButton({\n      primary,\n      disabled,\n      css,\n      ...props\n    }: ToolbarButtonProps) {\n      return (\n        <button\n          {...props}\n          disabled={disabled}\n          css={[\n            styles.button,\n            primary && styles.primary,\n            disabled && styles.disabled,\n            css,\n          ]}\n        />\n      );\n    }\n\n\nFor reusable components, Fluentic can go further with slots, scopes, and `combineStyle(...)`.\n\n\n\n    import {\n      bindScope,\n      combineStyle,\n      style,\n      type StyleProp,\n      type StyleTheme,\n    } from '@fluentic/style';\n\n    const buttonStyles = {\n      root: style.slot({\n        display: 'inline-flex',\n        alignItems: 'center',\n        gap: 8,\n        border: 0,\n        borderRadius: 8,\n        padding: '8px 12px',\n        backgroundColor: '#2563eb',\n        color: 'white',\n      }).hover({\n        opacity: 0.9,\n      }),\n\n      icon: style.slot({\n        display: 'inline-flex',\n        width: 16,\n        height: 16,\n      }),\n\n      label: style.slot({\n        fontWeight: 650,\n        lineHeight: 1,\n      }),\n    };\n\n    type ButtonProps = {\n      children: React.ReactNode;\n      css?: StyleProp;\n      icon?: React.ReactNode;\n      theme?: StyleTheme;\n    };\n\n    function BaseButton(props: ButtonProps) {\n      const css = combineStyle(\n        buttonStyles,\n        bindScope(buttonStyles.root, props.theme),\n      );\n\n      return (\n        <button css={[css.root, props.css]}>\n          {props.icon ? <span css={css.icon}>{props.icon}</span> : null}\n          <span css={css.label}>{props.children}</span>\n        </button>\n      );\n    }\n\n\nThen variants can target public slots instead of reaching through DOM structure:\n\n\n\n    const dangerButton = style.scope([\n      buttonStyles.root({\n        backgroundColor: '#dc2626',\n      }),\n\n      buttonStyles.label({\n        fontWeight: 700,\n      }),\n    ]);\n\n    const compactButton = style.scope([\n      buttonStyles.root({\n        gap: 6,\n        padding: '6px 10px',\n      }),\n\n      buttonStyles.label({\n        fontSize: 13,\n      }),\n    ]);\n\n    function Button({\n      danger,\n      compact,\n      theme,\n      ...props\n    }: ButtonProps & {\n      danger?: boolean;\n      compact?: boolean;\n    }) {\n      return (\n        <BaseButton\n          {...props}\n          theme={[\n            danger && dangerButton,\n            compact && compactButton,\n            theme,\n          ]}\n        />\n      );\n    }\n\n\nThe important part is not only the exact syntax.\n\nThe important part is the model:\n\n  * styles are authored as structured values\n  * styles are composed explicitly\n  * conditional styles are visible\n  * component parts can be named with slots\n  * variants can target slots without depending on DOM structure\n  * selectors are available, but they are not the whole styling model\n  * CSS is extracted for production builds\n\n\n\nThat is the balance I want.\n\nReact Native simplicity.\n\nCSS power.\n\nModern React output.\n\n##  Why Not Just Use Existing Tools?\n\nThis is a fair question.\n\nThere are many good styling tools now.\n\nSome are utility-first.\n\nSome are CSS-in-TypeScript.\n\nSome are zero-runtime.\n\nSome are compiler-first.\n\nSome are design-system focused.\n\nSome are close to CSS Modules with better ergonomics.\n\nI am not building Fluentic because I think all of them are bad.\n\nI am building it because I want a very specific feel.\n\nThe feel of React Native style composition, adapted to React web.\n\nThe thing I miss is not only extraction, type safety, or colocated styles.\n\nIt is the simple confidence of this line:\n\n\n\n    style={[base, variant, state, props.style]}\n\n\nThat mental model has been living rent-free in my head for years.\n\nWhen I came back to React web, I wanted that feeling again.\n\nBut I also wanted hover, focus, media queries, selectors, SSR, extraction, component slots, and CSS output.\n\nSo Fluentic is my attempt to connect those worlds.\n\n##  What I Am Trying To Avoid\n\nFluentic is also shaped by the things I want to avoid from my own past web code.\n\nI want to avoid class names becoming a private styling API between parent and child components.\n\nI want to avoid parent styles reaching too deeply into child structure.\n\nI want to avoid selectors that make markup hard to change.\n\nI want to avoid styling systems where I cannot easily answer “which style wins?”\n\nI want to avoid too much runtime styling work.\n\nI want to avoid cleverness that feels good today and suspicious six months later.\n\nThat last one is important.\n\nA lot of styling decisions feel great when writing them.\n\nThe real test is how they feel when changing them later.\n\n##  Fluentic Is Still Early\n\nFluentic is still early.\n\nI am still exploring the API, constraints, compiler behavior, and the right balance between CSS power and React Native-like explicitness.\n\nBut the direction is clear:\n\n> React styling should feel more like composing values, and less like maintaining invisible relationships between class names and DOM structure.\n\nThat is the idea I want to test.\n\nNot whether CSS can do everything. It can.\n\nNot whether extraction is useful. It is.\n\nThe question is:\n\nCan styling for component-heavy React apps feel predictable again?\n\nCan we keep the parts of CSS that make the web powerful, while making everyday component styling feel closer to the React Native model?\n\nThat is what I am building with Fluentic.\n\nYou can check out Fluentic here:\n\n  * GitHub: https://github.com/fluentic-dev/style\n  * Docs: https://fluenticstack.com/style\n\n\n\nIf this problem feels familiar, especially if you have worked on both React web and React Native apps, I would love to hear what you think.\n\nWhich parts of React styling become harder to change over time?\n\nWhich parts of React Native styling do you wish existed on React web?\n\nAnd where do you think this approach would fall short?\n\nThat feedback would be much more useful than stars alone.\n\nThough, of course, I will not pretend stars hurt.",
  "title": "Bringing React Native Style Composition to React"
}