ui
ui copied to clipboard
[feat]: Dynamic add new variants
Feature description
Problem
Currently some component have fixed variant. For example: const badgeVariants = cva(, const buttonVariants = cva( so I find some solution to add dynamic variant with minimum cost to modify ShadCN UI code.
I don't know if this is a good idea before proceeding to modify the code and create a pull request.
This solution need update any component in /components/ui that used = cva(. So the end-user (developer) cant override variant by update ONLY 1 file custom-vars.ts. It need shadcn tool (npx shadcn@latest) create/update (question) /components/ui/custom-vars.ts if file not exits.
This is example for update component button
Add merge variant function / customVars
For example customVars.button is override button variants. I use chat GPT, so maybe _deepMerge fn work not correctly.
// /src/components/ui/custom-vars.ts
type GetObjDifferentKeys<
T,
U,
T0 = Omit<T, keyof U> & Omit<U, keyof T>,
T1 = {
[K in keyof T0]: T0[K];
},
> = T1;
type GetObjSameKeys<T, U> = Omit<T | U, keyof GetObjDifferentKeys<T, U>>;
type MergeTwoObjects<
T,
U,
T0 = GetObjDifferentKeys<T, U> & { [K in keyof GetObjSameKeys<T, U>]: DeepMergeTwoTypes<T[K], U[K]> },
T1 = { [K in keyof T0]: T0[K] },
> = T1;
export type DeepMergeTwoTypes<T, U> = [T, U] extends [{ [key: string]: unknown }, { [key: string]: unknown }]
? MergeTwoObjects<NonNullable<T>, NonNullable<U>>
: NonNullable<T> | NonNullable<U>;
function _deepMerge<T extends object, U extends object>(target: T, source: U): DeepMergeTwoTypes<T, U> {
for (const key of Object.keys(source) as Array<keyof U>) {
if (source[key] instanceof Object && key in target) {
(target as any)[key] = _deepMerge((target as any)[key], source[key] as any);
} else {
(target as any)[key] = source[key];
}
}
return target as any;
}
export function mergeVariants<T, U>(baseConfig: T, customConfig: U): DeepMergeTwoTypes<T, U> {
return _deepMerge(baseConfig as any, customConfig as any) as any;
}
export const customVars = {
button: {
variants: {
variant: {
success: 'bg-success text-white hover:bg-success/80',
},
},
},
};
Modify button.tsx
import { cn } from '@/lib/utils';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import * as React from 'react';
// UPDATE: Import fn
import { customVars, mergeVariants } from './custom-vars';
const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
// UPDATED: do mergeVariants
mergeVariants(
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
customVars.button || {},
),
);
Usage button
Expected: Code should not throw error for variant='success'
<Button variant='success'>Success</Button>
Update CSS
@layer base {
:root {
/* Define new variables */
--success: 100 77% 44%;
--success-foreground: 102 85% 34%;
}
}
Update tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
theme: {
extend: {
colors: {
success: {
DEFAULT: 'hsl(var(--success))',
foreground: 'hsl(var(--success-foreground))',
},
}
}
}
}
Affected component/components
Alert, Badge, Button, Label, Sheet, Toast, Toggle
Additional Context
Additional details here...
Before submitting
- [X] I've made research efforts and searched the documentation
- [X] I've searched for existing issues and PRs
i need this
You can just change the variants in the cva, you own the code after you install a component, thats the point of ShadcnUI.
This issue has been automatically marked as stale due to two years of inactivity. It will be closed in 7 days unless there’s further input. If you believe this issue is still relevant, please leave a comment or provide updated details. Thank you.
No stale. Something went wrong with the stale bot.
I can totally understand the request. I try to always extend the component somehow —this makes updates in the future much simpler.
So it would be nice to have an option to extend specific styles like the variants of the badge component.
For merging variants, I was able to use extendTailwindMerge to safely merge custom utility classes with the cn utility: https://github.com/shadcn-ui/ui/discussions/6939
While I'm here, has anyone figured out a solution for overriding the defaultVariants for components? I'm thinking of setting up a function to set the defaultVariants for components using a single config to change common variants like borderRadius, size, color, etc. Seems like there might need to be a build step so the components are packaged with defaults specified for the app that consumes the components.
Although my example is for a monorepo use case, I think it might be useful for the component registry to have a way to update defaultVariants for components that use cva. I was thinking there could be a defaultVariants object inside of components.json to make this configurable in one place.