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

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-base 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:
-
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 to2
, which is used for heading levels.className
: Allows for additional CSS classes.htmlFor
: Captures thefor
attribute typically used with labels.
-
Base Class Retrieval: It retrieves the base styling classes associated with the specified
variant
from thetextStyles
object. If the variant isn’t found, it defaults to the"textMD"
variant. -
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. -
Tag Resolution for Headings: If the specified
tag
is one of the heading elements (h1
,h2
,h3
,h4
,h5
,h6
), it adjusts thetag
based on thelevel
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 thetag
prop. - Combined Classes: The
class
attribute is set tocombinedClasses
, 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-base 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
Unlock all themes for $199$139 and own them forever! Includes lifetime
updates, new themes, unlimited projects, and support
