Components

Description

Added in v3.2.0

Allows you to define components, using CSS-in-JS

Setup

tailwind.config.js
const { Components } = require('@area17/a17-tailwind-plugins');

module.exports = {
  ...
  plugins: [Components],
  theme: {
    components: {
      ".rich-text": {
        selectors: {
          h2: "f-h2 mt-40",
          h3: "f-h3 mt-40",
          h4: "f-h4 mt-40",
          p: "f-body mt-20",
          ul: "f-body mt-20 list-disc pl-16",
          "ul ul": "mt-0",
          code: "inline-block f-code text-code bg-code px-4 py-2 mt-20",
          "* code": "inline-block mt-0",
          pre: "block mt-20 f-code text-code bg-code p-8 max-h-400 overflow-y-scroll",
          "code + pre": "mt-0",
          a: "text-accent underline",
          "a:hover": "text-primary",
          "a:focus": "text-primary",
          hr: "border-tertiary my-40"
        }
      },
    }
  }
  ...
};

Config object keys

theme.components is essentially a object of selectors. Each entry can either be set to a string of classes to @apply, or an object containing apply, attributes, selectors, breakpoints and variants keys:

tailwind.config.js
selector: {
  apply: "",
  attributes: {},
  selectors: {},
  breakpoints: {}
}

Every child selector can also be set to a string of classes to @apply, or an object containing apply, attributes, selectors, breakpoints and variants keys.

Comma separated values and psuedo selectors will also be correctly parsed:

tailwind.config.js
components: {
  ".rich-text, .wysiwyg": {
    selectors: {
      a: "text-accent underline",
      "a:hover, a:focus": "text-primary"
    }
  },
}

Output

Using a config like:

tailwind.config.js
components: {
  ".rich-text": {
    selectors: {
      p: "f-body mt-20",
      a: "text-accent underline",
      "a:hover": "text-primary",
      "a:focus": "text-primary",
    }
  },
}

You would get an output like:

app.css
.rich-text p {
  margin-top: 1.25rem;
  font-family: var(--sans);
  font-size: 0.875rem;
  line-height: 1.7;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  --bold-weight: 600;
}

.rich-text p b, .rich-text p strong {
  font-weight: var(--bold-weight);
  font-weight: var(--bold-weight);
}

@media (min-width: 650px) {
  .rich-text p {
    font-size: 1rem;
  }
}

.rich-text a {
  color: var(--blue-03);
  text-decoration: underline;
}

.rich-text a:hover {
  color: var(--grey-90);
}

.rich-text a:focus {
  color: var(--grey-90);
}

You could also style that link like:

tailwind.config.js
components: {
  ".rich-text": {
    selectors: {
      p: "f-body mt-20",
      a: {
        apply: "text-accent underline",
        "selectors": {
          ":hover, :focus": "text-primary",
        }
      }
    }
  },
}

More complex example:

tailwind.config.js
components: {
  ".btn": {
    apply: "f-body flex justify-center items-center m-0 border-2 appearance-none",
    attributes: {
      "--padding-x": "16px",
      "--min-xy": "42px",
      "--icon-xy": "24px",
      "--icon-pad": "8px",
      "min-width": "var(--min-xy)",
      height: "var(--min-xy)",
      "padding-right": "var(--padding-x)",
      "padding-left": "var(--padding-x)",
      "transition": "color 200ms ease-in-out, background-color 200ms ease-in-out, border 200ms ease-in-out"
    },
    breakpoints: {
      lg: {
        attributes: {
          "--padding-x": "24px",
          "--min-xy": "48px"
        }
      }
    },
    selectors: {
      ":hover": {
        apply: "cursor-pointer"
      },
      "svg": {
        apply: "inline-block"
      }
    },
    "variants": {
      "primary": {
        attributes: {
          "border-color": "var(--grey-90)",
          "background-color": "var(--grey-54)",
          color: "var(--white)"
        },
        selectors: {
          ":hover, :focus, :active": {
            attributes: {
              "background-color": "var(--grey-90)",
              color: "var(--white)"
            }
          },
          ":focus, :active": {
            attributes: {
              "border-color": "var(--blue-05)"
            }
          },
          ":focus[data-focus-method='key']": {
            attributes: {
              "outline-color": "var(--blue-05) !important"
            }
          }
        }
      },
      "secondary": {
        attributes: {
          "border-color": "var(--grey-90)",
          "background-color": "var(--white)",
          color: "var(--grey-90)"
        },
        selectors: {
          ":hover, :focus, :active": {
            attributes: {
              "background-color": "var(--grey-54)"
            }
          },
          ":focus, :active": {
            attributes: {
              "border-color": "var(--blue-05)"
            }
          },
          ":focus[data-focus-method='key']": {
            attributes: {
              "outline-color": "var(--blue-05) !important"
            }
          }
        }
      }
    }
  },
  ".rich-text": {
    breakpoints: {
      md: {
        selectors: {
          "> *:not(pre)": {
            attributes: {
              "max-width": "80%"
            }
          }
        }
      },
      lg: {
        selectors: {
          "> *:not(pre)": {
            attributes: {
              "max-width": "60%"
            }
          }
        }
      }
    },
    selectors: {
      h1: "f-h1",
      h2: "f-h2 mt-40",
      h3: "f-h3 mt-40",
      h4: "f-h4 mt-40",
      p: "f-body mt-20",
      ul": "f-body mt-20 list-disc pl-16",
      "ul ul": "mt-0",
      code: "inline-block f-code text-code bg-code px-4 py-2 mt-20",
      "* code": "inline-block mt-0",
      pre: "block mt-20 f-code text-code bg-code p-8 max-h-400 overflow-y-scroll",
      "code + pre": "mt-0",
      a: {
        apply: "text-accent underline",
        selectors: {
          ":hover, :focus": {
            apply: "text-primary"
          }
        }
      },
      hr: "border-tertiary my-40"
    }
  }
}

Your Tailwind build will error out if you @apply classes that aren't defined or if you use @screen with breakpoints that don't exist.

Note: this plugin doesn't attempt to validate your input.