← Back to all tutorials

How to create a typography component with variants with Astro JS and Tailwind CSS

typography Navigation
Published and written on Oct 28 2024 by Michael Andreuzza

Hello eveyrone, today we are making a typography component for your Astro Js and Tailwind CSS project.

Why do we need a typography component?

Consistent typography is key to keeping your website looking clean and organized. Without it, things can start to look messy fast. A typography component helps keep text styling uniform across your whole site. The idea here is to make it both flexible and well-defined, so you get a good balance between structure and creativity, since not everything is just black and white. ‘

Lets get started by creating our component

Create your file for the component, in this we are calling it <Text /> we will be using the tag prop to specify the HTML tag to render and the variant prop to specify the variant of the text.

The Props

This section allows you to customize your typography component using various props. You can choose the HTML tag for semantic correctness, define style variants, indicate heading levels for accessibility, and add custom CSS classes for additional styling. Other props help link labels to form controls, assign unique IDs for targeting, and set URLs for anchor tags.

Using these props, you can create a flexible typography component that fits your project’s requirements while keeping a consistent look and feel.

NOTE: While you can define a link tag a and add a href attribute, I would create a separate component for links, because sometimes this ones look like buttons. Is a way to create a much cleaner component. Why did I add here? Well so you can see that it’s possible.

export interface Props {
  tag?:
    | "h1"
    | "h2"
    | "h3"
    | "h4"
    | "h5"
    | "h6"
    | "p"
    | "a"
    | "em"
    | "div"
    | "span"
    | "small"
    | "label"
    | "strong"
    | "summary"
    | "blockquote";
  variant?: string;
  level?: number;
  class?: string;
  for?: string;
  id?: string;
  href?: string;
}

The text styles

These are the styles I decided to use, but you can define your own ones, I have defined many levels, but is because I like to have it all prepared in case I need it. Do you need to create so many? Well most likely not, but it does not hurt to have them there just in case.

const textStyles: { [key: string]: string } = {
  display6XL:
    "text-4xl leading-tight tracking-tight sm:text-7xl md:text-9xl lg:text-[12rem]",
  display5XL:
    "text-4xl leading-tight tracking-tight sm:text-7xl md:text-8xl lg:text-[10rem]",
  display4XL:
    "text-4xl leading-tight tracking-tight sm:text-7xl md:text-8xl lg:text-9xl",
  display3XL:
    "text-4xl leading-tight tracking-tight sm:text-6xl md:text-7xl lg:text-8xl",
  display2XL:
    "text-4xl leading-tight tracking-tight sm:text-5xl md:text-6xl lg:text-7xl",
  displayXL:
    "text-3xl leading-tight tracking-tight sm:text-4xl md:text-5xl lg:text-6xl",
  displayLG:
    "text-2xl leading-tight tracking-tight sm:text-3xl md:text-4xl lg:text-5xl",
  displayMD:
    "text-xl leading-tight tracking-tight sm:text-2xl md:text-3xl lg:text-4xl",
  displaySM: "text-lg leading-tight sm:text-xl md:text-2xl lg:text-3xl",
  displayXS: "text-base leading-tight sm:text-lg md:text-xl lg:text-2xl",
  textXL: "text-lg leading-normal sm:text-xl md:text-2xl",
  textLG: "text-base leading-normal sm:text-lg md:text-xl",
  textBase: "text-base leading-normal",
  textMD: "text-md leading-normal ",
  textSM: "text-sm leading-normal ",
  textXS: "text-xs leading-normal ",
};

Break down

This code snippet is part of your Astro component and serves several important functions:

  1. Destructuring Props: It extracts values from the Astro.props object, assigning default values if they are not provided. This includes:

    • tag: Defaults to "p" (paragraph) if no tag is specified.
    • variant: Defaults to "textMD" for styling if no variant is provided.
    • level: Defaults to 2, which is used for heading levels.
    • className: Allows for additional CSS classes.
    • htmlFor: Captures the for attribute typically used with labels.
  2. Base Class Retrieval: It retrieves the base styling classes associated with the specified variant from the textStyles object. If the variant isn’t found, it defaults to the "textMD" variant.

  3. Combining Classes: It combines the base classes with any additional classes passed in via the className prop. The result is a single string of classes that can be used for styling the component.

  4. Tag Resolution for Headings: If the specified tag is one of the heading elements (h1, h2, h3, h4, h5, h6), it adjusts the tag based on the level prop. It ensures that the heading level is between 1 and 6, enforcing proper semantic HTML structure.

In summary, this code snippet allows for flexible and customizable typography in your Astro component, enabling you to specify how text should be displayed while maintaining semantic correctness and styling consistency.

const {
  tag: Tag = "p", // default to paragraph
  variant = "textMD", // default to textMD variant
  level = 2, // default to level 2 for headings
  class: className = "",
  for: htmlFor, // Capture 'for' attribute
  ...rest
} = Astro.props;
// Get the base classes for the variant
const baseClasses = textStyles[variant] || textStyles.textMD; // Default to textMD if not found
// Combine base classes with any additional custom classes
const combinedClasses = `${baseClasses} ${className}`.trim();
// Adjust the tag based on the level for headings
let resolvedTag: string = Tag; // Ensure resolvedTag is a string
if (["h1", "h2", "h3", "h4", "h5", "h6"].includes(Tag)) {
  resolvedTag = `h${Math.min(Math.max(level, 1), 6)}`; // Enforce level to be between 1 and 6
}

Rendering the component

Rendering the Typography Component

This section of the code renders the typography component using the specified HTML tag (<Tag>). It applies the combined CSS classes for styling and uses slots to allow for flexible content placement:

  • Dynamic Tag: The <Tag> element renders based on the tag prop.
  • Combined Classes: The class attribute is set to combinedClasses, ensuring proper styling.
  • Slots:
    • <slot name="left-icon" />: For optional content on the left.
    • <slot />: For the main text content.
    • <slot name="right-icon" />: For optional content on the right.

This structure provides versatility in how text and icons can be displayed within the component.

<Tag
  class={combinedClasses}
  {...rest}>
  <slot name="left-icon" />
  <slot />
  <slot name="right-icon" />
</Tag>

The full component code:

---
export interface Props {
  tag?:
    | "p"
    | "a"
    | "span"
    | "small"
    | "div"
    | "strong"
    | "em"
    | "blockquote"
    | "summary"
    | "label"
    | "h1"
    | "h2"
    | "h3"
    | "h4"
    | "h5"
    | "h6";
  variant?: string;
  level?: number;
  class?: string;
  for?: string;
  id?: string;
  href?: string;
}
const textStyles: { [key: string]: string } = {
  display6XL:
    "text-4xl leading-tight tracking-tight sm:text-7xl md:text-9xl lg:text-[12rem]",
  display5XL:
    "text-4xl leading-tight tracking-tight sm:text-7xl md:text-8xl lg:text-[10rem]",
  display4XL:
    "text-4xl leading-tight tracking-tight sm:text-7xl md:text-8xl lg:text-9xl",
  display3XL:
    "text-4xl leading-tight tracking-tight sm:text-6xl md:text-7xl lg:text-8xl",
  display2XL:
    "text-4xl leading-tight tracking-tight sm:text-5xl md:text-6xl lg:text-7xl",
  displayXL:
    "text-3xl leading-tight tracking-tight sm:text-4xl md:text-5xl lg:text-6xl",
  displayLG:
    "text-2xl leading-tight tracking-tight sm:text-3xl md:text-4xl lg:text-5xl",
  displayMD:
    "text-xl leading-tight tracking-tight sm:text-2xl md:text-3xl lg:text-4xl",
  displaySM: "text-lg leading-tight sm:text-xl md:text-2xl lg:text-3xl",
  displayXS: "text-base leading-tight sm:text-lg md:text-xl lg:text-2xl",
  textXL: "text-lg leading-normal sm:text-xl md:text-2xl",
  textLG: "text-base leading-normal sm:text-lg md:text-xl",
  textBase: "text-base leading-normal",
  textMD: "text-md leading-normal ",
  textSM: "text-sm leading-normal ",
  textXS: "text-xs leading-normal ",
};
const {
  tag: Tag = "p", // default to paragraph
  variant = "textMD", // default to textMD variant
  level = 2, // default to level 2 for headings
  class: className = "",
  for: htmlFor, // Capture 'for' attribute
  ...rest
} = Astro.props;
// Get the base classes for the variant
const baseClasses = textStyles[variant] || textStyles.textMD; // Default to textMD if not found
// Combine base classes with any additional custom classes
const combinedClasses = `${baseClasses} ${className}`.trim();
// Adjust the tag based on the level for headings
let resolvedTag: string = Tag; // Ensure resolvedTag is a string
if (["h1", "h2", "h3", "h4", "h5", "h6"].includes(Tag)) {
  resolvedTag = `h${Math.min(Math.max(level, 1), 6)}`; // Enforce level to be between 1 and 6
}
---
<Tag
  class={combinedClasses}
  {...rest}>
  <slot name="left-icon" />
  <slot />
  <slot name="right-icon" />
</Tag>

Using the Component on Your Site

To use the typography component in your site, you can specify the desired properties when rendering it. For example:

<Text
  tag="p"
  variant="display6XL"
>
  Display 6XL
</Text>

Conclusion

In this tutorial, we created a versatile typography component for your Astro and Tailwind CSS project. By using props, you can easily customize how your text looks and behaves, ensuring it fits well with your site’s design.

With the examples provided, you can now integrate this component into your website, enhancing your text’s visual appeal and accessibility. Feel free to modify it further to meet your specific needs!

/Michael Andreuzza

Did you like this tutorial? Please share it with your friends!

Get lifetime access to every theme available today for $199 and own them forever. Plus, new themes, lifetime updates, use on unlimited projects and enjoy lifetime support.

No subscription required!