Design Systems & Library Authoring #145
Replies: 6 comments 24 replies
-
I just wanted to start off by sharing a corrected and shortened version of your initial StyleX example. I share this to explain the "idea" behind media queries and pseudo classes in StyleX. import stylex from "@stylexjs/stylex";
const styles = stylex.create({
grid: {
display: "grid",
gap: 8,
gridTemplateColumns: {
default: '1fr',
"@media (min-width: 640px) and (max-width: 767px)": "repeat(2, 1fr)",
"@media (min-width: 768px) and (min-width: 1229px)": "repeat(3, 1fr)",
"@media (min-width: 1230px)": "repeat(4, 1fr)",
},
// You should probably just use:
// gridTemplateColumns: 'repeat(auto-fill, minmax(360px, 1fr))',
},
item: {
backgroundColor: "#f5f5f5",
paddingBlock: 4,
paddingInline: 8,
},
});
export default function Example() {
const names = ["Morty", "Rick", "Summer", "Jerry", "Beth"];
return (
<div {...stylex.props(styles.grid)}>
{names.map((name) => (
<div key={name} {...stylex.props(styles.item)}>
{name}
</div>
))}
</div>
);
} In StyleX, there are no "mobile styles", "tablet styles" and "desktop styles. In StyleX, you just have styles, and some values can adapt to the screen size. This API also encourages you to think about layout holistically and at all screen sizes before you start adding special cases. Regarding terseness, I shared a concept to recreate an experience similar to Tailwind in StyleX on Twitter. https://x.com/naman34/status/1734074956882026894?s=20 This works today but could end up generating a lot of unused CSS. I have some ideas on how this pattern can be optimized. Another approach that people have asked for would be to allow "inline" styles. Some existing solutions have used an API lie this. export default function Example() {
const names = ["Morty", "Rick", "Summer", "Jerry", "Beth"];
return (
<div
css={{
display: "grid",
gap: 8,
gridTemplateColumns: 'repeat(auto-fill, minmax(360px, 1fr))',
}}
>
{names.map((name) => (
<div key={name} css={{
backgroundColor: "#f5f5f5",
paddingBlock: 4,
paddingInline: 8,
}}>
{name}
</div>
))}
</div>
);
} We could look into something along these lines, but so far I don't have an API that would work well. Regarding your concept using an imported design system componentsThis could be implemented in StyleX today, but it would generate less efficient code as it would be dependent on dynamic styles which involve setting inline styles. Since you want to pass in arbitrary values, you need to pass in styles compiled with Also, I'm generally against using shorthands like |
Beta Was this translation helpful? Give feedback.
-
Great topic for discussion! Very curious to hear the suggested patterns. For inline style overrides, have you seen CSS Hooks? It looks quite promising. |
Beta Was this translation helpful? Give feedback.
-
I respect the decision to go this route, but IMO this will result in much more refactoring work than otherwise would be needed for existing projects - should they wish to migrate to StyleX - if their styles have been defined using those pretty common patterns already. I can see that being a pretty hard selling point for someone considering to adopt StyleX. |
Beta Was this translation helpful? Give feedback.
-
Regarding this approach mentioned by @nmn : export default function Example() {
const names = ["Morty", "Rick", "Summer", "Jerry", "Beth"];
return (
<div
css={{
display: "grid",
gap: 8,
gridTemplateColumns: 'repeat(auto-fill, minmax(360px, 1fr))',
}}
>
{names.map((name) => (
<div key={name} css={{
backgroundColor: "#f5f5f5",
paddingBlock: 4,
paddingInline: 8,
}}>
{name}
</div>
))}
</div>
);
} I wrote a POC as a plugin for Vite + Vue. I'm not an expert on static analysis, bundlers nor anything on those lines, so I took the easy/dangerous route for me: good old Regex matching. So, basically, I've introduced a I haven't used it extensively yet, but it's doing its job for now and you can even mix inline with predefined styles. If anyone with deeper knowledge of Vite's compiler could try this approach, I thing it can be a good direction to follow. // The Vite plugin
{
name: 'vite:stylex-attr',
enforce: 'pre',
transform: (code, id) => {
const attrMatchRegex = /:(stylex|css)="([^"]*)"/g
const attrMatches = code.match(attrMatchRegex)
if (!attrMatches) {
return code
}
let styles = ''
// Handle :stylex attributes
code = code
.replace(attrMatchRegex, (attrMatch, attrName, contents) => {
let newContents = contents.replace(/\{[\s\S]*?\}/g, (objMatch) => {
const styleName = uuid()
// Create a new stylex variable
styles += `'${styleName}': ${objMatch},`
// Replace object literals with stylex calls
return `styles['${styleName}']`
})
// Unwrap array
if (/^\[/.test(newContents)) {
newContents = newContents.replace(/^\[|\]$/g, '')
}
// Replace :stylex attributes with :class attributes
return `:class="stylex(${newContents})"`
})
const stylexDefinitionRegex = /<script(?![^>]*\bsetup\b)[^>]*>([\s\S]*?stylex\.create\(\{([\s\S]*?)\}\)[\s\S]*?)<\/script>/g
if (!stylexDefinitionRegex.test(code)) {
return (code += `
<script>
const styles = stylex.create({${styles}})
</script>
`)
}
// Modify the object passed to stylex.create()
return code.replace(stylexDefinitionRegex, (scriptTag, scriptContents, stylesObject) => {
const modifiedScriptContent = scriptContents.replace(/(stylex\.create\(\{)([\s\S]*?)(\}\))/, (match, start, props, end) => {
const newProps = (props)
? `${props}, `
: ''
return `${start}${newProps}${styles}${end}`
})
// Replace the original script content in the SFC
return scriptTag.replace(scriptContents, modifiedScriptContent)
})
}
} // Component
<template>
<q-banner
rounded
:css="[
{
backgroundColor: 'var(--bg-color, #000000)'
},
styles[type]
]"
>
<template #avatar>
<i-mdi-information
:css="{
fontSize: '2rem',
opacity: 0.3
}"
/>
</template>
<template #action>
<q-btn
flat
color="white"
label="Dismiss"
@click="$emit('dismiss')"
/>
</template>
<template
v-for="(slot, index) of Object.keys($slots)"
:key="index"
#[slot]
>
<slot :name="slot" />
</template>
</q-banner>
</template>
<script setup>
import { colors } from '../assets/tokens.stylex.js'
defineProps({
type: {
type: String,
default: 'info'
}
})
defineEmits(['dismiss'])
</script>
<script>
const styles = stylex.create({
success: {
'--bg-color': colors.success_500
},
info: {
'--bg-color': colors.info_500
},
warning: {
'--bg-color': colors.warning_500
},
error: {
'--bg-color': colors.danger_500
}
})
</script>
|
Beta Was this translation helpful? Give feedback.
-
I'm trying to build a scaffolding of UI library using Vite and StyleX too but without success just yet. Can anyone share link to a repo that is working? |
Beta Was this translation helpful? Give feedback.
-
I’ve been tracking this project for a long time now. Congrats on the public launch! 🎉
My interest has been around using StyleX to build a design system foundation. At present, I don’t know that it’s suited for this
case. I’d love to hear your thoughts regarding using StyleX for library authoring.
Component API
I grant there a valid reasons regarding terseness. As it stands, this results in a lot of boilerplate in one-off layouts when compared to alternatives that developers are using.
For instance, we can go from 15 lines in a relatively simple layout to over 40 switching from Tailwind. That’s going to put a lot of people off.
Example using StyleX
Example using Tailwind
Concept using an imported design system components
Note that I’m using custom properties here. I’m guessing that it’s unavoidable to have to handle some form of precompiling to achieve this.
The Stitches API for overriding styles is roughly the vision I had for a design system foundation using StyleX. Styles are passed as a standard object, which is a lot terser than what StyleX currently allows, but still following the convention of CSS property naming. Several runtime-heavy libraries work in this manner.
I experimented a bit with a Babel plugin that extracts the CSS prop and nests it directly in a
stylex.create
call. I haven’t got this to work as of writing, but could be a solution to allow for this.Is this something you encountered? What are you thoughts on possible solutions for this?
Design tokens
One of the key things that is likely to become an issue — including for Meta — is design token distribution.
Currently only static/local values are allowed inside of
stylex.create
calls, which makes importing distributed themes and design tokens impossible.StyleX processes
.stylex.js
files, which is a nice solution. It reminds me of Vanilla Extract.I note that you’ve responded to this already, but wanted to ask if there’s consideration for loading/referencing external packages in the future?
One thing that came to mind was resolving static imports and inling them just in
.stylex.js
.Potentially a lot of work, I’ll grant, but this feels like one that’s going to come up a lot.
Beta Was this translation helpful? Give feedback.
All reactions