Motivation
Let's make Button with below 3 ways
JSXCSS
and @emotion/styled
, @emotion/react's css
Let's create one Button component for each.
type ButtonProps = Pick<ComponentPropsWithoutRef<'button'>, 'onClick' | 'children' | 'type'> & {
isLoading?: boolean
}
1. Button of JSXCSS
const JSXCSSButton = forwardRef<HTMLButtonElement, ButtonProps>(({ children, isLoading, ...rest }, ref) => (
<Stack.Horizontal
as={motion.button}
whileHover={{
scale: 0.98,
cursor: 'pointer',
}}
type="button"
spacing={16}
padding={[12, 16, 18]}
borderRadius={[12, 16, 18]}
backgroundColor="#0051ff"
border="none"
ref={ref}
{...rest}
>
{isLoading && (
<Flex.Center>
<Spinner />
</Flex.Center>
)}
<Flex.Center fontSize={[18, 20, 24]}>{children}</Flex.Center>
</Stack.Horizontal>
))
2. Button of @emotion/styled
const ButtonContainer = styled(motion.button)`
display: flex;
align-items: stretch;
gap: 16px;
padding: 12px;
border-radius: 12px;
background-color: #0051ff;
border: none;
@media (min-width: 768px) {
padding: 16px;
border-radius: 16px;
}
@media (min-width: 1440px) {
padding: 18px;
border-radius: 18px;
}
`
const ButtonTextWrapper = styled.div`
display: flex;
justify-content: center;
align-items: cetner;
font-size: 18px;
@media (min-width: 768px) {
font-size: 20px;
}
@media (min-width: 1440px) {
font-size: 24px;
}
`
const ButtonLoadingWrapper = styled.div`
display: flex;
justify-content: center;
align-items: cetner;
`
const StyledButton = forwardRef<HTMLButtonElement, ButtonProps>(({ children, isLoading, ...rest }, ref) => {
return (
<ButtonContainer
ref={ref}
{...rest}
whileHover={{
scale: 0.98,
cursor: 'pointer',
}}
>
{isLoading && (
<ButtonLoadingWrapper>
<Spinner />
</ButtonLoadingWrapper>
)}
<ButtonTextWrapper>{children}</ButtonTextWrapper>
</ButtonContainer>
)
})
3. Button of css prop(@emotion/react)
const CSSButton = forwardRef<HTMLButtonElement, ButtonProps>(({ children, isLoading, ...rest }, ref) => (
<motion.button
type="button"
whileHover={{
scale: 0.98,
cursor: 'pointer',
}}
ref={ref}
css={css`
display: flex;
align-items: center;
gap: 16px;
padding: 16px;
border-radius: 16px;
background-color: #0051ff;
border: none;
@media (min-width: 768px) {
padding: 16px;
border-radius: 16px;
}
@media (min-width: 1440px) {
padding: 18px;
border-radius: 18px;
}
`}
{...rest}
>
{isLoading && (
<div
css={css`
display: flex;
justify-content: center;
align-items: cetner;
`}
>
<Spinner />
</div>
)}
<div
css={css`
display: flex;
justify-content: center;
align-items: cetner;
font-size: 18px;
@media (min-width: 768px) {
font-size: 20px;
}
@media (min-width: 1440px) {
font-size: 24px;
}
`}
>
{children}
</div>
</motion.button>
))
Which one is the most intuitive?
Doesn't JSXCSS allow you to write layouts intuitively and declaratively with much shorter code than other methods?
It's why we made JSXCSS
Layout components (Box, Flex, Stack)
Supports declarative layouts such as Box, Flex and Stack.
const NeedLayoutComponent = () => (
<Stack.Vertical>
<Flex.Center />
<Box />
</Stack.Vertical>
)Polymorphic as prop
With polymorphic as prop, all components can be converted to any html tag, such as section, input, button, as well as div. This is also type-safe. also be easily integrated with other animation libraries (framer-motion, react-spring).
const TypesafeAsProp = () => (
<>
<Box
as="a"
href="https://example.com" // no error
/>
<Box
as="div" // div have no href prop
href="https://example.com" // type-safely, error will occured
/>
</>
)
import { motion } from 'framer-motion'
const IntegrationWith3rdParty = () => (
<Box
as={motion.div} // Integration with 3rd party library
whileTap={{ scale: 1.2 }} // type-safely, you can use props of as component
/>
)Responsive css prop (MediaQueryProvider, useMediaQuery)
Responsive UI can be implemented very easily.
// As the width of the screen gets wider, the font size will gradually increase from 16px to 20px to 36px.
const ResponsiveUI = () => <Box fontSize={[16, 20, 36]} />