Have you ever been building a project, and realized there was a certain way you styed your component or UI, which you don't want to repeat, but just reuse in multiple places?. I faced this sort of problem while building my invoice management application. I used shadcn/ui for this project to use components that are consistent across my UI, and if you are familiar with shadcn/ui, it has an <Input /> component it ships with which you can reuse across your application when building forms.
The problem I had with this component though is that the style I was trying to achieve required me to copy paste the same styles in the classname prop each time I used the <input /> component, which is not very efficient.
import { type SubmitEvent } from "react";
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
function AddInvoice() {
function handleSubmit(e: SubmitEvent<HTMLFormElement>) {
e.preventDefault();
}
return (
<form onSubmit={handleSubmit} className="relative mt-7 px-3 lg:px-0">
<Field className="relative mt-4 md:col-span-3">
<FieldLabel htmlFor="address1">
Street Address
</FieldLabel>
<Input
id="address1"
name="fromAddress"
className="border-glaucous/25 rounded py-6 px-4"
/>
</Field>
<Field className="relative mt-4 md:col-span-3">
<FieldLabel htmlFor="clientName">
Client Name
</FieldLabel>
<Input
id="clientName"
name="clientName"
className="border-glaucous/25 rounded py-6 px-4"
/>
</Field>
<Field className="relative mt-4 md:col-span-3">
<FieldLabel htmlFor="postalCode">
Postal Code
</FieldLabel>
<Input
id="postalCode"
name="postalCode"
className="border-red-400 rounded-xl py-6 px-4"
/>
</Field>
</form>
);
}
export default AddInvoice;
Wouldn't it be better if I have these styles set in my component by default, then just change it where necessary?. After making some research and diving into how other components in shadcn/ui are structured, I came across something called Variants. A variant in the context of shadcn/ui is basically different ways a component can be styled. The <Button /> component for example has variants which allows us use buttons in different ways, but the <Input /> component doesn't ship with variants by default, so that led me to creating a custom variant for my <Input /> component. This is how the <Input /> component looks by default:
import * as React from "react";
import { cn } from "cn";
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return (
<input
type={type}
data-slot="input"
className={cn(
"h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className,
)}
{...props}
/>
);
}
export { Input };
This might look a bit different depending on the style shadcn/ui is using for your project which can either be radix-ui or base-ui, you can confirm this in the components.json file. With this structure, we can modify the class names the way we want to match our design, so wherever we use the <Input /> component, we have the same UI. The issue starts when we need different styles of our input element, for example, we might want a black background in one location, and a red background with a black border in another location (that's a strange combo though 😂), we can easily achieve that by just overwriting the classnames with our own, but to make things scale better and avoid using the same set of classnames repeatedly, we use variants to box reusable set of styles together. Thanks to how flexible shadcn/ui is, you can add variants to any UI component. Here's how to add a variant to the <Input /> component:
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const inputVariants = cva(
"h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-red-aura md:text-sm dark:bg-input dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 text-primary font-bold",
{
variants: {
variant: {
primary: "border-glaucous/25 rounded py-6 px-4",
},
},
defaultVariants: {
variant: "primary",
},
},
);
function Input({
variant = "primary",
className,
type,
...props
}: React.ComponentProps<"input"> &
VariantProps<typeof inputVariants> & {
asChild?: boolean;
}) {
return (
<input
type={type}
data-slot="input"
className={cn(inputVariants({ variant, className }))}
{...props}
/>
);
}
export { Input };
As you can see, I added a variant called inputVariants, and named the first variant primary, you can add other variants as well such as secondary, outline etc., and give them their appropriate style, I also indicated the primary variant as the default whenever the component is used. The class-variance-authority package gives us the ability to add different variants for a particular UI component without changing it's architecture, which allows us change the different ways our component can appear. With this, I can style my input component in different ways for different scenarios without having to be boxed into using a specific set of class names. This keeps the code clean, and makes it easier to add more styles in the future.
I wonder why this isn't the default in shadcn/ui for the <Input /> component, and it's a feature I plan on submitting a pull request for. It doesn't make sense to keep overwriting class names each time I want to use the component, it's more flexible to have variants in my opinion.
Conclusion
Using variants with a shadcn/ui component in your project makes it easier to use components across your application without having to change too many things in your app. This was how I created the solution to my own problem, this way, eliminating the need to repeat the same set of styles across the application. How do you usually use shadcn/ui in your project?, have you ever encountered something like this you needed to create a custom variant for?
Top comments (0)