Skip to content

Latest commit

 

History

History
416 lines (311 loc) · 10.1 KB

prop-styling-guide.md

File metadata and controls

416 lines (311 loc) · 10.1 KB

The prop styling guide

Basic styling

Use Twin’s tw prop to add Tailwind classes onto jsx elements:

import 'twin.macro'

const Component = () => (
  <div tw="flex w-full">
    <div tw="w-1/2"></div>
    <div tw="w-1/2"></div>
  </div>
)
  • Use the tw prop when conditional styles aren’t needed
  • Any import from twin.macro activates the tw prop
  • Remove the need for an import with babel-plugin-twin

Conditional styling

To add conditional styles, nest the styles in an array and use the css prop:

import tw from 'twin.macro'

const Component = ({ hasBg }) => (
  <div
    css={[
      tw`flex w-full`, // Add base styles first
      hasBg && tw`bg-black`, // Then add conditional styles
    ]}
  >
    <div tw="w-1/2" />
    <div tw="w-1/2" />
  </div>
)
TypeScript example
import tw from 'twin.macro'

interface ComponentProps {
  hasBg?: string
}

const Component = ({ hasBg }: ComponentProps) => (
  <div
    css={[
      tw`flex w-full`, // Add base styles first
      hasBg && tw`bg-black`, // Then add conditional styles
    ]}
  >
    <div tw="w-1/2" />
    <div tw="w-1/2" />
  </div>
)
  • Twin doesn’t own the css prop, the prop comes from your css-in-js library
  • Adding values to an array makes it easier to define base styles, conditionals and vanilla css
  • Use multiple lines to organize styles within the backticks (template literals)

Overriding styles

Use the tw prop after the css prop to add any overriding styles:

import tw from 'twin.macro'

const Component = () => (
  <div css={tw`text-white`} tw="text-black">
    Has black text
  </div>
)

Keeping jsx clean

It’s no secret that when tailwind class sets become larger, they obstruct the readability of other jsx props.

To clean up the jsx, lift the styles out and group them as named entries in an object:

import tw from 'twin.macro'

const styles = {
  container: ({ hasBg }) => [
    tw`flex w-full`, // Add base styles first
    hasBg && tw`bg-black`, // Then add conditional styles
  ],
  column: tw`w-1/2`,
}

const Component = ({ hasBg }) => (
  <section css={styles.container({ hasBg })}>
    <div css={styles.column} />
    <div css={styles.column} />
  </section>
)
TypeScript example
import tw from 'twin.macro'

interface ContainerProps {
  hasBg?: boolean;
}

const styles = {
  container: ({ hasBg }: ContainerProps) => [
    tw`flex w-full`, // Add base styles first
    hasBg && tw`bg-black`, // Then add conditional styles
  ],
  column: tw`w-1/2`,
}

const Component = ({ hasBg }: ContainerProps) => (
  <section css={styles.container({ hasBg })}>
    <div css={styles.column} />
    <div css={styles.column} />
  </section>
)

Variants with many values

When a variant has many values (eg: variant="light/dark/etc"), name the class set in an object and use a prop to grab the entry containing the styles:

import tw from 'twin.macro'

const containerVariants = {
  // Named class sets
  light: tw`bg-white text-black`,
  dark: tw`bg-black text-white`,
  crazy: tw`bg-yellow-500 text-red-500`,
}

const styles = {
  container: ({ variant = 'dark' }) => [
    tw`flex w-full`,
    containerVariants[variant], // Grab the variant style via a prop
  ],
  column: tw`w-1/2`,
}

const Component = ({ variant }) => (
  <section css={styles.container({ variant })}>
    <div css={styles.column} />
    <div css={styles.column} />
  </section>
)
TypeScript example

Use the TwStyle import to type tw blocks:

import tw, { TwStyle } from 'twin.macro'

type WrapperVariant = 'light' | 'dark' | 'crazy'

interface ContainerProps {
  variant?: WrapperVariant
}

const containerVariants: Record<WrapperVariant, TwStyle> = {
  // Named class sets
  light: tw`bg-white text-black`,
  dark: tw`bg-black text-white`,
  crazy: tw`bg-yellow-500 text-red-500`,
}

const styles = {
  container: ({ variant = 'dark' }: ContainerProps) => [
    tw`flex w-full`,
    containerVariants[variant], // Grab the variant style via a prop
  ],
  column: tw`w-1/2`,
}

const Component = ({ variant }: ContainerProps) => (
  <section css={styles.container({ variant })}>
    <div css={styles.column} />
    <div css={styles.column} />
  </section>
)

Interpolation workaround

Due to Babel limitations, tailwind classes and arbitrary properties can’t have any part of them dynamically created.

So interpolated values like this won’t work:

<div tw="mt-${spacing === 'sm' ? 2 : 4}" /> // Won't work with tailwind classes
<div tw="[margin-top:${spacing === 'sm' ? 2 : 4}rem]" /> // Won't work with arbitrary properties

This is because babel doesn’t know the values of the variables and so twin can’t make a conversion to css.

Instead, define the classes in objects and grab them using props:

import tw from 'twin.macro'

const styles = { sm: tw`mt-2`, lg: tw`mt-4` }

const Component = ({ spacing = 'sm' }) => <div css={styles[spacing]} />

Or combine vanilla css with twins theme import:

import { theme } from 'twin.macro'

// Use theme values from your tailwind config
const styles = { sm: theme`spacing.2`, lg: theme`spacing.4` }

const Component = ({ spacing = 'sm' }) => (
  <div css={{ marginTop: styles[spacing] }} />
)

Or we can always fall back to vanilla css, which can interpolate anything:

import 'twin.macro'

const Component = ({ width = 5 }) => <div css={{ maxWidth: `${width}rem` }} />

Custom selectors (Arbitrary variants)

Use square-bracketed arbitrary variants to style elements with a custom selector:

import tw from 'twin.macro'

const buttonStyles = tw`
  bg-black
  [> i]:block
  [> span]:(text-blue-500 w-10)
`

const Component = () => (
  <button css={buttonStyles}>
    <i>Icon</i>
    <span>Label</span>
  </button>
)
More examples
// Style the current element based on a theming/scoping className
;<body className="dark-theme">
  <div tw="[.dark-theme &]:(bg-black text-white)">Dark theme</div>
</body>

// Add custom group selectors
;<button className="group" disabled>
  <span tw="[.group:disabled &]:text-gray-500">Text gray</span>
</button>

// Add custom height queries
;<div tw="[@media (min-height: 800px)]:hidden">
  This window is less than 800px height
</div>

// Use custom at-rules like @supports
;<div tw="[@supports (display: grid)]:grid">A grid</div>

// Style the current element based on a dynamic className
const Component = ({ isLarge }) => (
  <div className={isLarge && 'is-large'} tw="text-base [&.is-large]:text-lg">
    ...
  </div>
)

Custom class values (Arbitrary values)

Custom values can be added to many tailwind classes by using square brackets to define the custom value:

;<div tw="top-[calc(100vh - 2rem)]" />
// ↓ ↓ ↓ ↓ ↓ ↓
<div css={{
  "top": "calc(100vh - 2rem)"
}} />

Read more about Arbitrary values →

Custom css

Basic css is added using arbitrary properties or within vanilla css which supports more advanced use cases like dynamic/interpolated values.

Simple css styling

To add simple custom styling, use arbitrary properties:

// Set css variables
<div tw="[--my-width-variable:calc(100vw - 10rem)]" />

// Set vendor prefixes
<div tw="[-webkit-line-clamp:3]" />

// Set grid areas
<div tw="[grid-area:1 / 1 / 4 / 2]" />

Use arbitrary properties with variants or twins grouping features:

<div tw="block md:(relative [grid-area:1 / 1 / 4 / 2])" />

Arbitrary properties also work with the tw import:

import tw from 'twin.macro'
;<div
  css={tw`
    block
    md:(relative [grid-area:1 / 1 / 4 / 2])
  `}
/>
  • Add a bang to make the custom css !important: ![grid-area:1 / 1 / 4 / 2]
  • Arbitrary properties can have camelCase properties: [gridArea:1 / 1 / 4 / 2]

Advanced css styling

The css prop accepts a sass-like syntax, allowing both custom css and tailwind styles with values that can come from your tailwind config:

import tw, { css, theme } from 'twin.macro'

const Components = () => (
  <input
    css={[
      tw`text-blue-500 border-2`,
      css`
        -webkit-tap-highlight-color: transparent; /* add css styles */
        background-color: ${theme`colors.red.500`}; /* use the theme import to add config values */
        &::selection {
          ${tw`text-purple-500`}; /* style with tailwind classes */
        }
      `,
    ]}
  />
)

But it’s often cleaner to use an object to add styles as it avoids the interpolation cruft seen above:

import tw, { css, theme } from 'twin.macro'

const Components = () => (
  <input
    css={[
      tw`text-blue-500 border-2`,
      css({
        WebkitTapHighlightColor: 'transparent', // css properties are camelCased
        backgroundColor: theme`colors.red.500`, // values don’t require interpolation
        '&::selection': tw`text-purple-500`, // single line tailwind selector styling
      }),
    ]}
  />
)

Learn more

Resources


‹ Documentation