Styled-jsx is an amazing tool to create new styled components from plain HTML elements, however, in my experience, integrating it with already existing components can be cumbersome.
Newcomers can easily be deceived into attempting something like this:
const MyComponent = () => (<div>
Click this red button <Button/>
<style jsx>{`Button { background: red } `}</style>
</div>)
To someone familiar with how this library works, this triggers all the alarms. The Button won't be affected by the style for a variety of reasons: <Button/> isn't a known HTML tag and it doesn't really have to correspond to any HTML at all. Furthermore, the Button component doesn't even receive the local scope's _className_, because it does not pass the test in babel.js, saying only JSX tags starting in lowercase get it. Indeed, why should _className_ be passed to the Button component, when styled-jsx has no idea whether Button even accepts a _className_ prop.
To clarify: my Button is a styled-component. It does accept a _className_ prop and returns a single HTML element with all CSS classes, including the one passed from props, concatenated and applied:
const Button = styled.button`apppearance:none; border: 1px solid black; padding: 6px;`
<Button className="hello"/> // -> renders in HTML to <button class="hello sc-adf415"></button>
Some other libraries and components (React Transition Group and React Router spring to mind) may treat _className_ more liberally, either by allowing the programmer to supply multiple _classNames_ for different functions, or by calling the prop differently.
One possible course of action is to introduce a global _className_ that will be applied to Button, but only inside MyComponent. Maybe like this:
const MyComponent = () => (<div>
Click this red button <Button className="red-button"/>
<style jsx>{`div :global(.red-button) { background: red } `}</style>
</div>)
This has a major drawback: the introduction of a global _className_ leads to possible naming collisions, which are to be avoided in component architectures. A partial remedy is to use child selectors (>) instead of descendant selectors ([space]), but that breaks component encapsulation.
The resolve tag is a great option to create a stand-alone css class that can be applied in any way the developer wants. If I were to fix the above example with resolve, it'd look like this:
const { className, styles } = css.resolve`button { background: red }`;
const MyComponent = () => (<div>
Click this red button <Button className={className}/>
</div>)
My objection to this is that for each third-party component you want to style, you'll have to create new variable and you'll have to keep them each in a different variable. Instead <style jsx/> keeps all styles in one place: _inside_ the actual MyComponent definition.
This solution negates the simplicity introduced by <style jsx/> and trades it for _className_ reusability. In my experience, the goals of using components coincide with the elimination of the need to reuse _classNames_. Those are usually used in just one place - in the component you're designing - and no place else.
_css.resolve gets verbose way too quickly and styles are not kept in one place_
const { redClassName, redStyles } = css.resolve`button { background: red }`;
const { blueClassName, blueStyles } = css.resolve`button { background: blue }`;
const MyComponent = () => (<div>
Click the blue button <Button className={redClassName}/> <Button className={blueClassName}/>
<style jsx>{`div { width: 600px; margin: auto; } `}</style>
</div>)
In my opinion, this library should strive to copy the na茂ve way of writing third party styles. I'd like the best to write code that resembles this the most:
const MyComponent = () => (<div>
Click this red button <Button/>
<style jsx>{`Button { background: red } `}</style>
</div>)
If the babel transform is changed so that JSX_CLASSNAME is an alias of state.className, it becomes possible to write code like this:
const MyComponent = () => (<div>
Click this red button <Button className={`${JSX_CLASSNAME} button`}/>
<style jsx>{`.button { background: red } `}</style>
</div>)
This approach requires nothing more than
either adding JSX_CLASSNAME's variable declaration before the styled block (implemented here)
or replacing all JSX_CLASSNAME identifiers with state.className (implemented here)
Variants on this include
1. changing JSX_CLASSNAME to any other variable name
2. using an 'imported' function:
import {jsx} from 'styled-jsx';
const MyComponent = () => (<div>
Click this red button <Button className={jsx('button')}/>
<style jsx>{`.button { background: red } `}</style>
</div>)
That's it from me for now. Other propositions and discussion of viability welcome below
it can already work if you alias the component
const StyledButton = Button;
<StyledButton className="button">
hi
<style jsx>{`.button { color: red }`}</style>
</StyledButton>
@giuseppeg is this documented anywhere? I've seen hints at this functionality in code, but I don't remember seeing it in a README.
Anyway, this is only viable for components that accept a className prop in the common way. What about components that take classNames or titleClassName and bodyClassName? I see a lot of potential in enabling these to be styled easily.
this workaround is undocumented because in these cases it is better to be explicit. It is in the library only to make dynamic tags work. See the Heading example here https://github.com/zeit/styled-jsx/releases/tag/v3.0.0 I think that the current set of features allows to style components from 3rd parties therefore I am goig to close this issue
Can this be possible?
// page.js
// a contracted field liked styledCls
<Button styledCls="my-btn">click me!</Button>
<style jsx>{`
.my-btn {
background: red
}
`}</style>
// Button.js
// the 'styledCls' inject into 'className' props;
const { text, className } = props; // className = 'jsx-12345678 my-btn'
const prefixCls = 'component-btn';
<button className={classNames(prefixCls, className)}>{text}</button>
// render
// <button class="jsx-12345678 component-btn my-btn">click me!</button>
// <style>.jsx-12345678.my-btn { background: red }</style>
@czy0729 That could conceivably overwrite a styledCls prop in a third-party component from a library.
What I eventually did was that I forked styled-jsx into my own npm package comfy-styled-jsx. Now I can do this
import {jsx} from 'comfy-styled-jsx';
const MyConfirmBox = () => (
<div>
<h3>Press to confirm</h3>
<Button className={jsx`large-button`}>confirm</Button>
<style jsx>{`
.large-button { padding: 20px; }
`}</style>
</div>
)
/**
* Renders:
* <div>
* <h3>Press to confirm</h3>
* <Button className="jsx-12345678 large-button">confirm</Button>
* </div>
*/
You can easily change the import's name to avoid possible collisions. I've found that this is the best solution I could come up with.
before, I always take a unique name as a prefixCls and pass to 3rd-party, and it is awful.
@Peping your solution is nice, awesome, thank you
it can already work if you alias the component
const StyledButton = Button; <StyledButton className="button"> hi <style jsx>{`.button { color: red }`}</style> </StyledButton>
this workaround is undocumented because in these cases it is better to be explicit. It is in the library only to make dynamic tags work. See the Heading example here https://github.com/zeit/styled-jsx/releases/tag/v3.0.0 I think that the current set of features allows to style components from 3rd parties therefore I am goig to close this issue
This workaround doesn't appear to work for me, but if it did work, it should definitely be documented. I've been stuck for hours trying to apply slick-carousel/slick/slick.css to a react-slick component.
I also encountered issue https://github.com/zeit/styled-jsx/issues/495, which also seems to be lacking documentation.
Only way I've been able to make react-slick work is by copying slick-carousel/slick/slick.css into the project and make it available with the css.global template tag. I don't know why, but if I import the slick.css file directly, and use type: 'global' in the webpack loader, the contents include enclosing curly braces, and the target elements remain unaffected.
Most helpful comment
@czy0729 That could conceivably overwrite a
styledClsprop in a third-party component from a library.What I eventually did was that I forked styled-jsx into my own npm package
comfy-styled-jsx. Now I can do thisYou can easily change the import's name to avoid possible collisions. I've found that this is the best solution I could come up with.