I have setup and published to a private server a StoryBook design system using ThemeUI that contains components. One such component is the button shown below.
import React, { FC } from "react";
import { Button as ButtonUI, SxStyleProp } from "theme-ui";
export const Button: FC<ButtonProps> = ({ onClick, children, variant, sx, disabled, onMouseOver, onMouseOut }) => {
return (
<ButtonUI
disabled={disabled || false}
variant={variant || "primary"}
onClick={onClick}
sx={{...sx}}
onMouseOver={onMouseOver || (() => {})}
onMouseOut={onMouseOut || (() => {})}
>
{children}
</ButtonUI>
);
};
export type ButtonProps = {
/**
* The action to perform when the button is clicked
*/
onClick: () => void;
/**
* The contents of the button
*/
children?: any;
/**
* The type of button
*/
variant?: string;
/**
* custom styles for the button
*/
sx?: SxStyleProp;
/**
* If the button is disabled
*/
disabled?: boolean;
/**
* The action to perform if the mouse moves over the button
*/
onMouseOver?: () => void;
/**
* The action to perform if the mouse moves off the button
*/
onMouseOut?: () => void;
};
Button.defaultProps = {
variant: "primary",
disabled: false
};
When I import this component into my React App in a separate project the component renders but all properties in the sx prop are ignored...
/** @jsx jsx */
import { Flex, jsx } from "theme-ui";
import Modal from "../Modal";
import { Button } from "<<<PRIVATE SERVER>>>";
/**
* Renders a popup the gives the player the option to logout
* @param title the heading of the modal
* @param confirmString the text for the confirm button
* @param cancelString the text for the cancel button
* @param confirmAction the action to perform when the user confirms
* @param cancelAction the action to perform when the user cancels
*/
export default function LogoutModal({ title, confirmString, cancelString, confirmAction, cancelAction }: Props) {
return (
<Modal
title={title}
closeAction={cancelAction}
children={
<Flex>
<Button
onClick={confirmAction}
sx={{
mr: 1
}}
variant="negative">{confirmString}</Button>
<Button
onClick={cancelAction}
sx={{
ml: 1
}}
variant="secondary">{cancelString}</Button>
</Flex>
}
/>
);
}
interface Props {
title: string;
confirmString: string;
cancelString: string;
confirmAction: () => void;
cancelAction: () => void;
}
So the button renders but without the applicable margin (or any other styles I add in the sx prop. Does anyone have any idea why this would be the case?
Interestingly if I call the component somewhere else in Storybook (rather than when imported into a seperate project) the sx prop works as expected.
sx
gets transformed to a className
by theme-uiSee the working CodeSandbox demo, then continue reading.
From the documentation:
Under the hood, Theme UI uses a custom pragma comment that converts a theme-aware
sx
prop into a style object and passes it to Emotion'sjsx
functions. Thesx
prop only works in modules that have defined a custom pragma at the top of the file, which replaces the default Reactjsx
functions. This means you can control which modules in your application opt into this feature without the need for a Babel plugin or additional configuration. This is intended as a complete replacement for the Emotion custom JSX pragma.
So it's first needed to have the pragma comment at the top of the file, and import the right jsx
function:
/** @jsx jsx */
import { jsx, Button as ButtonUI, SxStyleProp } from 'theme-ui';
The sx
prop isn't passed down to the component, it's in fact converted to CSS and a className
is auto-generated and applied to the component.
className
So if you'd like the parent component applying custom style to the Button
component via the sx
prop, you need to pass down the className
prop.
export const Button: FC<ButtonProps> = ({
onClick,
children,
variant,
// This does nothing...
// sx,
disabled,
onMouseOver,
onMouseOut,
// Make sure to pass the className down to the button.
className, // or ...props
}) => (
<ButtonUI
className={className}
// {...props} // if you want any other props
sx={{ /* any other style can be applied here as well */ }}
disabled={disabled || false}
variant={variant || 'primary'}
onClick={onClick}
onMouseOver={onMouseOver || (() => {})}
onMouseOut={onMouseOut || (() => {})}
>
{children}
</ButtonUI>
);
Personal recommendation is to not use some kind of hack passing down sx
content down. I've been working on a huge project professionally that uses theme-ui and never have we ever needed anything else than pass the className
down to merge styles automatically.