Skip to content

Instantly share code, notes, and snippets.

@arthurgeron
Created June 12, 2024 15:48
Show Gist options
  • Save arthurgeron/083f9280262fe32e308109959bdffca3 to your computer and use it in GitHub Desktop.
Save arthurgeron/083f9280262fe32e308109959bdffca3 to your computer and use it in GitHub Desktop.
radix-ui-themes-with-tailwind refactored to work with @radix-ui/themes V3
import { withOptions } from 'tailwindcss/plugin';
import * as colors from 'tailwindcss/colors';
import { accentColors, grayColors } from '@radix-ui/themes/src/props';
/*
Exposes internal Radix tokens to Tailwind
Based on the deprecated radix-ui-themes-with-tailwind@1.2.6, which was made for Radix Themes V2, here refactored for V3.
/*
/*
Radix unfortunately does not export these values
*/
const colorsRegular = [
'tomato',
'red',
'ruby',
'crimson',
'pink',
'plum',
'purple',
'violet',
'iris',
'indigo',
'blue',
'cyan',
'teal',
'jade',
'green',
'grass',
'brown',
'orange',
] as const;
const colorsBright = ['sky', 'mint', 'lime', 'yellow', 'amber'] as const;
const colorsMetal = ['gold', 'bronze'] as const;
const accentColorsGrouped = [
{ label: 'Regulars', values: colorsRegular },
{ label: 'Brights', values: colorsBright },
{ label: 'Metals', values: colorsMetal },
{ label: 'Gray', values: ['gray'] as const },
];
export const accentColorNames: string[] = [];
export const grayColorNames: string[] = [];
const radixColorScales = 12;
type RadixColorScales = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
accentColorsGrouped.map((group) => {
accentColorNames.push(...group.values.filter((color) => color !== 'gray'));
});
grayColors.map((color) => {
if (color !== 'auto') {
grayColorNames.push(color);
}
});
export function getColorTokenName(
number: RadixColorScales,
useTailwindColorNames?: boolean,
alpha?: boolean,
): number | string {
const map: Record<number, number> = {
1: 25,
2: 50,
3: 100,
4: 200,
5: 300,
6: 400,
7: 500,
8: 600,
9: 700,
10: 800,
11: 900,
12: 950,
} as const;
if (!useTailwindColorNames) {
return alpha ? `${number}A` : number;
}
return alpha ? (`${map[number]}A` as string) : (map[number] as number);
}
export const getColorDefinitions = (
color: string,
alpha?: boolean,
useTailwindColorNames?: boolean,
) => {
const colors = Array.from(Array(radixColorScales).keys()).reduce(
(acc, _, i) => {
acc[
getColorTokenName(
(i + 1) as RadixColorScales,
useTailwindColorNames,
alpha,
)
] = `var(--${color}-${alpha ? 'a' : ''}${i + 1})`;
return acc;
},
{} as Record<string, string>,
);
if (!alpha) {
colors[`${getColorTokenName(9, useTailwindColorNames, alpha)}-contrast`] =
`var(--${color}-9-contrast)`;
colors.surface = `var(--${color}-surface)`;
colors.DEFAULT = `var(--${color}-9)`;
if (color === 'accent') {
colors.surface = 'var(--color-surface-accent)';
}
}
return colors;
};
type RadixColors = Exclude<
(typeof accentColors)[number] | (typeof grayColors)[number],
'auto'
>;
export const tailwindColorsToRadixMap: Record<
'zinc' | 'neutral' | 'stone' | 'emerald' | 'fuchsia' | 'rose',
RadixColors | Record<string, string>
> = {
zinc: 'sand',
neutral: 'sage',
stone: 'sand',
emerald: 'grass',
fuchsia: 'plum',
rose: 'crimson',
};
const radixRadiusToTailwindMap = {
1: 'xxs',
2: 'xs',
3: 'sm',
4: 'md',
5: 'lg',
6: 'xl',
} as const;
export function getRadiusTokenName(
radius: keyof typeof radixRadiusToTailwindMap,
useTailwindColorNames?: boolean,
): string | number {
return useTailwindColorNames ? radixRadiusToTailwindMap[radius] : radius;
}
export type RadixThemePluginOptions = {
useTailwindColorNames?: boolean;
useTailwindRadiusNames?: boolean;
mapMissingTailwindColors?: boolean | Partial<typeof tailwindColorsToRadixMap>;
};
const radixThemePlugin = withOptions(
() => {
return ({ addBase }) => {
addBase({
'*': {
outlineColor: 'currentColor',
},
'html, body': {
height: '100%',
'-webkit-font-smoothing': 'antialiased',
'-moz-osx-font-smoothing': 'grayscale',
},
em: {
fontFamily: 'var(--em-font-family)',
fontWeight: 'var(--em-font-weight)',
fontSize: 'calc(var(--em-font-size-adjust) * 1em)',
fontStyle: 'var(--em-font-style)',
lineHeight: '1.25',
letterSpacing:
'calc(var(--em-letter-spacing) + var(--letter-spacing, var(--default-letter-spacing)))',
},
h6: {
fontFamily: 'var(--heading-font-family)',
fontWeight: 'var(--font-weight-bold)',
fontSize:
'calc(var(--font-size-4) * var(--heading-font-size-adjust))',
letterSpacing:
'calc(var(--letter-spacing-4) + var(--heading-letter-spacing))',
lineHeight: 'var(--heading-line-height-4)',
},
h5: {
fontFamily: 'var(--heading-font-family)',
fontWeight: 'var(--font-weight-bold)',
fontSize:
'calc(var(--font-size-5) * var(--heading-font-size-adjust))',
letterSpacing:
'calc(var(--letter-spacing-5) + var(--heading-letter-spacing))',
lineHeight: 'var(--heading-line-height-5)',
},
h4: {
fontFamily: 'var(--heading-font-family)',
fontWeight: 'var(--font-weight-bold)',
fontSize:
'calc(var(--font-size-6) * var(--heading-font-size-adjust))',
letterSpacing:
'calc(var(--letter-spacing-6) + var(--heading-letter-spacing))',
lineHeight: 'var(--heading-line-height-6)',
},
h3: {
fontFamily: 'var(--heading-font-family)',
fontWeight: 'var(--font-weight-bold)',
fontSize:
'calc(var(--font-size-7) * var(--heading-font-size-adjust))',
letterSpacing:
'calc(var(--letter-spacing-7) + var(--heading-letter-spacing))',
lineHeight: 'var(--heading-line-height-7)',
},
h2: {
fontFamily: 'var(--heading-font-family)',
fontWeight: 'var(--font-weight-bold)',
fontSize:
'calc(var(--font-size-8) * var(--heading-font-size-adjust))',
letterSpacing:
'calc(var(--letter-spacing-8) + var(--heading-letter-spacing))',
lineHeight: 'var(--heading-line-height-8)',
},
h1: {
fontFamily: 'var(--heading-font-family)',
fontWeight: 'var(--font-weight-bold)',
fontSize:
'calc(var(--font-size-9) * var(--heading-font-size-adjust))',
letterSpacing:
'calc(var(--letter-spacing-9) + var(--heading-letter-spacing))',
lineHeight: 'var(--heading-line-height-9)',
},
});
};
},
({
useTailwindColorNames = true,
useTailwindRadiusNames = true,
mapMissingTailwindColors = true,
}: RadixThemePluginOptions) => {
function generateTailwindColors(colorName: string) {
const c = {
...getColorDefinitions(colorName, false, useTailwindColorNames),
...getColorDefinitions(colorName, true, useTailwindColorNames),
};
if (grayColorNames.includes(colorName)) {
c[`${getColorTokenName(2, useTailwindColorNames, false)}-translucent`] =
`var(--${colorName}-2-translucent)`;
}
return c;
}
const allRadixColors = [...accentColorNames, ...grayColorNames].reduce<
Record<string, Record<string, string>>
>((acc, colorName) => {
acc[colorName] = { ...generateTailwindColors(colorName) };
return acc;
}, {});
let mappingsOfMissingTailwindColors = {};
if (typeof mapMissingTailwindColors === 'boolean') {
mappingsOfMissingTailwindColors = {
zinc: generateTailwindColors('sand'),
neutral: generateTailwindColors('sage'),
stone: generateTailwindColors('mauve'),
emerald: generateTailwindColors('grass'),
fuchsia: generateTailwindColors('plum'),
rose: generateTailwindColors('crimson'),
};
} else if (typeof mapMissingTailwindColors === 'object') {
mappingsOfMissingTailwindColors = {
zinc:
typeof mapMissingTailwindColors.zinc === 'string'
? generateTailwindColors(mapMissingTailwindColors.zinc)
: mapMissingTailwindColors.zinc,
neutral:
typeof mapMissingTailwindColors.neutral === 'string'
? generateTailwindColors(mapMissingTailwindColors.neutral)
: mapMissingTailwindColors.neutral,
stone:
typeof mapMissingTailwindColors.stone === 'string'
? generateTailwindColors(mapMissingTailwindColors.stone)
: mapMissingTailwindColors.stone,
emerald:
typeof mapMissingTailwindColors.emerald === 'string'
? generateTailwindColors(mapMissingTailwindColors.emerald)
: mapMissingTailwindColors.emerald,
fuchsia:
typeof mapMissingTailwindColors.fuchsia === 'string'
? generateTailwindColors(mapMissingTailwindColors.fuchsia)
: mapMissingTailwindColors.fuchsia,
rose:
typeof mapMissingTailwindColors.rose === 'string'
? generateTailwindColors(mapMissingTailwindColors.rose)
: mapMissingTailwindColors.rose,
};
}
return {
darkMode: 'class',
theme: {
fontSize: {
xs: [
'var(--font-size-1)',
{
letterSpacing: 'var(--letter-spacing-1)',
lineHeight: 'var(--line-height-1)',
},
],
sm: [
'var(--font-size-2)',
{
letterSpacing: 'var(--letter-spacing-2)',
lineHeight: 'var(--line-height-2)',
},
],
base: [
'var(--font-size-3)',
{
letterSpacing: 'var(--letter-spacing-3)',
lineHeight: 'var(--line-height-3)',
},
],
lg: [
'var(--font-size-4)',
{
letterSpacing: 'var(--letter-spacing-4)',
lineHeight: 'var(--line-height-4)',
},
],
xl: [
'var(--font-size-5)',
{
letterSpacing: 'var(--letter-spacing-5)',
lineHeight: 'var(--line-height-5)',
},
],
'2xl': [
'var(--font-size-6)',
{
letterSpacing: 'var(--letter-spacing-6)',
lineHeight: 'var(--line-height-6)',
},
],
'3xl': [
'var(--font-size-7)',
{
letterSpacing: 'var(--letter-spacing-7)',
lineHeight: 'var(--line-height-7)',
},
],
'4xl': [
'var(--font-size-8)',
{
letterSpacing: 'var(--letter-spacing-8)',
lineHeight: 'var(--line-height-8)',
},
],
'5xl': [
'var(--font-size-9)',
{
letterSpacing: 'var(--letter-spacing-9)',
lineHeight: 'var(--line-height-9)',
},
],
},
lineHeight: {
1: 'var(--line-height-1)',
2: 'var(--line-height-2)',
3: 'var(--line-height-3)',
4: 'var(--line-height-4)',
5: 'var(--line-height-5)',
6: 'var(--line-height-6)',
7: 'var(--line-height-7)',
8: 'var(--line-height-8)',
9: 'var(--line-height-9)',
none: '1',
tight: '1.25',
snug: '1.375',
normal: '1.5',
relaxed: '1.625',
loose: '2',
},
fontFamily: {
sans: 'var(--default-font-family)',
strong: 'var(--strong-font-family)',
heading: 'var(--heading-font-family)',
code: 'var(--code-font-family)',
em: 'var(--em-font-family)',
quote: 'var(--quote-font-family)',
},
letterSpacing: {
1: 'var(--letter-spacing-1)',
2: 'var(--letter-spacing-2)',
3: 'var(--letter-spacing-3)',
4: 'var(--letter-spacing-4)',
5: 'var(--letter-spacing-5)',
6: 'var(--letter-spacing-6)',
7: 'var(--letter-spacing-7)',
8: 'var(--letter-spacing-8)',
9: 'var(--letter-spacing-9)',
tighter: '-0.05em',
tight: '-0.025em',
normal: '0',
wide: '0.025em',
wider: '0.05em',
widest: '0.1em',
},
fontWeight: {
thin: '100',
extralight: '200',
light: 'var(--font-weight-light)',
normal: 'var(--font-weight-regular)',
medium: 'var(--font-weight-medium)',
semibold: '600',
bold: 'var(--font-weight-bold)',
extrabold: '800',
black: '900',
},
spacing: {
'0': '0px',
px: '1px',
'0.5': 'calc(2px * var(--scaling))',
'1': 'var(--space-1)',
'1.5': 'calc(6px * var(--scaling))',
'2': 'var(--space-2)',
'2.5': 'calc(10px * var(--scaling))',
'3': 'var(--space-3)',
'3.5': 'calc(14px * var(--scaling))',
'4': 'var(--space-4)',
'5': 'calc(20px * var(--scaling))',
'6': 'var(--space-5)',
'7': 'calc(28px * var(--scaling))',
'8': 'var(--space-6)',
'9': 'calc(36px * var(--scaling))',
'10': 'var(--space-7)',
'11': 'calc(44px * var(--scaling))',
'12': 'var(--space-8)',
'14': 'calc(56px * var(--scaling))',
'16': 'var(--space-9)',
'20': 'calc(80px * var(--scaling))',
'24': 'calc(96px * var(--scaling))',
'28': 'calc(112px * var(--scaling))',
'32': 'calc(128px * var(--scaling))',
'36': 'calc(144px * var(--scaling))',
'40': 'calc(160px * var(--scaling))',
'44': 'calc(176px * var(--scaling))',
'48': 'calc(192px * var(--scaling))',
'52': 'calc(208px * var(--scaling))',
'56': 'calc(224px * var(--scaling))',
'60': 'calc(240px * var(--scaling))',
'64': 'calc(256px * var(--scaling))',
'72': 'calc(288px * var(--scaling))',
'80': 'calc(320px * var(--scaling))',
'96': 'calc(384px * var(--scaling))',
},
borderRadius: {
none: '0px',
[getRadiusTokenName(1, useTailwindRadiusNames)]: 'var(--radius-1)',
[getRadiusTokenName(2, useTailwindRadiusNames)]: 'var(--radius-2)',
[getRadiusTokenName(3, useTailwindRadiusNames)]: 'var(--radius-3)',
DEFAULT: 'var(--radius-3)',
[getRadiusTokenName(4, useTailwindRadiusNames)]: 'var(--radius-4)',
[getRadiusTokenName(5, useTailwindRadiusNames)]: 'var(--radius-5)',
[getRadiusTokenName(6, useTailwindRadiusNames)]: 'var(--radius-6)',
full: '99999px',
},
colors: {
inherit: 'inherit',
transparent: 'transparent',
current: 'currentColor',
white: colors.white,
black: colors.black,
background: 'var(--color-background)',
surface: {
DEFAULT: 'var(--color-surface)',
accent: 'var(--color-surface-accent)',
},
overlay: 'var(--color-overlay)',
panel: {
solid: 'var(--color-panel-solid)',
translucent: 'var(--color-panel-translucent)',
},
selection: 'var(--color-selection-root)',
...allRadixColors,
...mappingsOfMissingTailwindColors,
accent: generateTailwindColors('accent'),
gray: generateTailwindColors('gray'),
},
},
};
},
);
export default radixThemePlugin;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment