Documentation
Snow Cone UI Installation Guide
Introduction
As the shadcn UI, this is also a collection of re-usable components that you can copy and paste into your apps. It is based in Radix UI Primitives and others unstyled libraries.
The biggest difference is that this colection follows the Material 3 Design principles and components are designed to be used in the M3 design system.
Getting Started
At the moment, the collection is only available for React and frameworks that use React as Next.js, Astro, Remix, Gatsby etc.
The components are written in TypeScript. So it is recommended using TypeScript for your project as well.
Installation
Typescript
Edit your tsconfig.json
file to resolve paths:
{
"compilerOptions": {
// ...
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
// ...
}
}
Tailwind CSS
For starters you will need to install Tailwind CSS in your project. If don’t have it yet, follow your framework guide to install it.
Once Tailwind CSS is installed, create a file to keep the theme, for example in src/config/theme.ts
and add the next code:
import { fontFamily } from 'tailwindcss/defaultTheme'
import type { OptionalConfig } from 'tailwindcss/types/config'
const theme = {
extend: {
container: {
center: true,
padding: {
DEFAULT: '1rem',
sm: '2rem',
lg: '4rem',
xl: '5rem',
'2xl': '6rem',
},
screens: {
'2xl': '1400px',
},
},
fontFamily: {
sans: ['Outfit Variable', ...fontFamily.sans],
},
opacity: {
4: '0.04',
8: '0.08',
12: '0.12',
16: '0.16',
38: '0.38',
},
borderRadius: {
'4xl': 'calc(var(--radius) * 4 + (2/3 * var(--radius)))',
'3xl': 'calc(var(--radius) * 4 + (1/3 * var(--radius)))',
'2xl': 'calc(var(--radius) * 2 + (2/3 * var(--radius)))',
xl: 'calc(var(--radius) * 2 + (1/3 * var(--radius)))',
lg: 'calc(var(--radius) + (1/3 * var(--radius)))',
md: 'var(--radius)',
sm: 'calc(2/3 * var(--radius))',
xs: 'calc(1/3 * var(--radius))',
},
fontSize: {
'display-lg': ['3.56rem', { lineHeight: '4rem' }],
'display-md': ['2.81rem', { lineHeight: '3.25rem' }],
'display-sm': ['2.25rem', { lineHeight: '2.75rem' }],
'headline-lg': ['2rem', { lineHeight: '2.5rem' }],
'headline-md': ['1.75rem', { lineHeight: '2.25rem' }],
'headline-sm': ['1.5rem', { lineHeight: '2rem' }],
'title-lg': ['1.375rem', { lineHeight: '1.75rem' }],
'title-md': ['1rem', { lineHeight: '1.5rem', fontWeight: 500 }],
'title-sm': ['0.875rem', { lineHeight: '1.25rem', fontWeight: 500 }],
'label-lg': ['0.875rem', { lineHeight: '1.25rem', fontWeight: 500 }],
'label-md': ['0.75rem', { lineHeight: '1rem', fontWeight: 500 }],
'label-sm': ['0.687rem', { lineHeight: '1rem', fontWeight: 500 }],
'body-lg': ['1rem', { lineHeight: '1.5rem' }],
'body-md': ['0.875rem', { lineHeight: '1.25rem' }],
'body-sm': ['0.75rem', { lineHeight: '1rem' }],
},
boxShadow: {
xs: '0px 1px 2px 0px hsl(var(--shadow) / 0.15), 0px 1px 2px 0px hsl(var(--shadow) / 0.2)',
sm: '0px 1px 4px 1px hsl(var(--shadow) / 0.1), 0px 1px 3px 0px hsl(var(--shadow) / 0.15)',
md: '0px 2px 6px 2px hsl(var(--shadow) / 0.1), 0px 1px 3px 0px hsl(var(--shadow) / 0.2)',
lg: '0px 4px 8px 3px hsl(var(--shadow) / 0.15), 0px 1px 3px 0px hsl(var(--shadow) / 0.3)',
xl: '0px 6px 10px 4px hsl(var(--shadow) / 0.15), 0px 2px 3px 0px hsl(var(--shadow) / 0.3)',
'2xl':
'0px 8px 12px 6px hsl(var(--shadow) / 0.15), 0px 4px 4px 0px hsl(var(--shadow) / 0.3)',
},
colors: {
primary: 'hsl(var(--primary))',
onPrimary: 'hsl(var(--onPrimary))',
primaryContainer: 'hsl(var(--primaryContainer))',
onPrimaryContainer: 'hsl(var(--onPrimaryContainer))',
secondary: 'hsl(var(--secondary))',
onSecondary: 'hsl(var(--onSecondary))',
secondaryContainer: 'hsl(var(--secondaryContainer))',
onSecondaryContainer: 'hsl(var(--onSecondaryContainer))',
tertiary: 'hsl(var(--tertiary))',
onTertiary: 'hsl(var(--onTertiary))',
tertiaryContainer: 'hsl(var(--tertiaryContainer))',
onTertiaryContainer: 'hsl(var(--onTertiaryContainer))',
error: 'hsl(var(--error))',
onError: 'hsl(var(--onError))',
errorContainer: 'hsl(var(--errorContainer))',
onErrorContainer: 'hsl(var(--onErrorContainer))',
success: 'hsl(var(--success))',
onSuccess: 'hsl(var(--onSuccess))',
successContainer: 'hsl(var(--successContainer))',
onSuccessContainer: 'hsl(var(--onSuccessContainer))',
info: 'hsl(var(--info))',
onInfo: 'hsl(var(--onInfo))',
infoContainer: 'hsl(var(--infoContainer))',
onInfoContainer: 'hsl(var(--onInfoContainer))',
warning: 'hsl(var(--warning))',
onWarning: 'hsl(var(--onWarning))',
warningContainer: 'hsl(var(--warningContainer))',
onWarningContainer: 'hsl(var(--onWarningContainer))',
background: 'hsl(var(--background))',
onBackground: 'hsl(var(--onBackground))',
surface: 'hsl(var(--surface))',
onSurface: 'hsl(var(--onSurface))',
surfaceVariant: 'hsl(var(--surfaceVariant))',
onSurfaceVariant: 'hsl(var(--onSurfaceVariant))',
outline: 'hsl(var(--outline))',
outlineVariant: 'hsl(var(--outlineVariant))',
shadow: 'hsl(var(--shadow))',
scrim: 'hsl(var(--scrim))',
inverseSurface: 'hsl(var(--inverseSurface))',
inverseOnSurface: 'hsl(var(--inverseOnSurface))',
inversePrimary: 'hsl(var(--inversePrimary))',
primaryFixed: 'hsl(var(--primaryFixed))',
primaryFixedDim: 'hsl(var(--primaryFixedDim))',
onPrimaryFixed: 'hsl(var(--onPrimaryFixed))',
secondaryFixed: 'hsl(var(--secondaryFixed))',
secondaryFixedDim: 'hsl(var(--secondaryFixedDim))',
onSecondaryFixed: 'hsl(var(--onSecondaryFixed))',
tertiaryFixed: 'hsl(var(--tertiaryFixed))',
tertiaryFixedDim: 'hsl(var(--tertiaryFixedDim))',
onTertiaryFixed: 'hsl(var(--onTertiaryFixed))',
surfaceDim: 'hsl(var(--surfaceDim))',
surfaceBright: 'hsl(var(--surfaceBright))',
surfaceContainerLowest: 'hsl(var(--surfaceContainerLowest))',
surfaceContainerLow: 'hsl(var(--surfaceContainerLow))',
surfaceContainer: 'hsl(var(--surfaceContainer))',
surfaceContainerHigh: 'hsl(var(--surfaceContainerHigh))',
surfaceContainerHighest: 'hsl(var(--surfaceContainerHighest))',
palette: {
primary: {
'0': '0 0% 0%',
'4': '153 100% 4%',
'5': '154 100% 4%',
'6': '157 100% 5%',
'10': '158 100% 6%',
'12': '161 100% 7%',
'14': '160 100% 8%',
'17': '162 100% 10%',
'20': '162 100% 11%',
'22': '162 100% 12%',
'24': '163 100% 13%',
'30': '163 100% 16%',
'40': '163 100% 21%',
'50': '164 100% 27%',
'60': '161 61% 40%',
'70': '158 48% 52%',
'80': '157 61% 64%',
'87': '156 78% 71%',
'90': '156 89% 75%',
'92': '155 98% 77%',
'94': '154 100% 84%',
'95': '152 100% 87%',
'96': '151 100% 90%',
'98': '145 100% 95%',
'99': '136 100% 98%',
'100': '0 0% 100%',
},
secondary: {
'0': '0 0% 0%',
'4': '153 100% 4%',
'5': '156 91% 4%',
'6': '157 84% 5%',
'10': '157 56% 8%',
'12': '157 47% 10%',
'14': '155 38% 11%',
'17': '155 31% 14%',
'20': '153 26% 16%',
'22': '153 24% 18%',
'24': '153 22% 20%',
'30': '153 17% 25%',
'40': '151 13% 34%',
'50': '149 10% 44%',
'60': '150 10% 54%',
'70': '149 14% 65%',
'80': '146 20% 75%',
'87': '148 30% 83%',
'90': '145 37% 86%',
'92': '148 43% 88%',
'94': '145 54% 91%',
'95': '145 62% 92%',
'96': '145 72% 93%',
'98': '145 100% 95%',
'99': '136 100% 98%',
'100': '0 0% 100%',
},
tertiary: {
'0': '0 0% 0%',
'4': '200 100% 5%',
'5': '199 100% 5%',
'6': '197 100% 6%',
'10': '196 100% 8%',
'12': '195 100% 9%',
'14': '196 100% 10%',
'17': '195 100% 12%',
'20': '195 77% 15%',
'22': '197 66% 17%',
'24': '196 57% 19%',
'30': '199 43% 25%',
'40': '199 30% 35%',
'50': '200 24% 45%',
'60': '200 24% 55%',
'70': '199 32% 65%',
'80': '201 48% 76%',
'87': '200 73% 84%',
'90': '201 91% 87%',
'92': '201 100% 90%',
'94': '201 100% 93%',
'95': '201 100% 94%',
'96': '203 100% 95%',
'98': '207 100% 98%',
'99': '216 100% 99%',
'100': '0 0% 100%',
},
neutral: {
'0': '0 0% 0%',
'4': '140 11% 5%',
'5': '150 13% 6%',
'6': '140 8% 7%',
'10': '140 6% 10%',
'12': '140 5% 12%',
'14': '140 4% 14%',
'17': '150 5% 16%',
'20': '140 3% 19%',
'22': '135 4% 20%',
'24': '140 3% 22%',
'30': '135 3% 27%',
'40': '120 2% 37%',
'50': '120 1% 46%',
'60': '120 1% 56%',
'70': '120 2% 67%',
'80': '100 3% 77%',
'87': '105 5% 85%',
'90': '90 7% 88%',
'92': '90 8% 91%',
'94': '100 8% 93%',
'95': '100 10% 94%',
'96': '90 15% 95%',
'98': '90 29% 97%',
'99': '90 50% 98%',
'100': '0 0% 100%',
},
neutralVariant: {
'0': '0 0% 0%',
'4': '150 33% 5%',
'5': '153 31% 6%',
'6': '150 24% 7%',
'10': '150 16% 10%',
'12': '150 14% 11%',
'14': '147 13% 13%',
'17': '147 11% 15%',
'20': '150 9% 18%',
'22': '147 9% 20%',
'24': '147 8% 21%',
'30': '147 7% 27%',
'40': '144 5% 36%',
'50': '140 4% 46%',
'60': '144 4% 56%',
'70': '140 5% 66%',
'80': '138 8% 77%',
'87': '133 11% 85%',
'90': '138 16% 88%',
'92': '132 20% 90%',
'94': '133 23% 92%',
'95': '133 27% 94%',
'96': '138 36% 95%',
'98': '132 63% 97%',
'99': '132 100% 98%',
'100': '0 0% 100%',
},
error: {
'0': '0 0% 0%',
'4': '359 100% 8%',
'5': '359 100% 9%',
'6': '359 100% 10%',
'10': '358 100% 13%',
'12': '358 100% 14%',
'14': '358 100% 16%',
'17': '357 100% 18%',
'20': '357 100% 21%',
'22': '357 100% 22%',
'24': '357 100% 24%',
'30': '356 100% 29%',
'40': '0 75% 42%',
'50': '2 73% 53%',
'60': '4 100% 64%',
'70': '6 100% 75%',
'80': '6 100% 84%',
'87': '7 100% 89%',
'90': '6 100% 92%',
'92': '7 100% 94%',
'94': '7 100% 95%',
'95': '9 100% 96%',
'96': '7 100% 97%',
'98': '7 100% 98%',
'99': '300 100% 99%',
'100': '0 0% 100%',
},
success: {
'0': '0 0% 0%',
'4': '130 100% 4%',
'5': '129 100% 4%',
'6': '130 100% 5%',
'10': '133 100% 6%',
'12': '133 100% 7%',
'14': '134 100% 8%',
'17': '136 100% 10%',
'20': '138 100% 11%',
'22': '138 100% 12%',
'24': '139 100% 13%',
'30': '137 80% 18%',
'40': '130 45% 29%',
'50': '126 33% 39%',
'60': '125 27% 49%',
'70': '123 34% 60%',
'80': '122 47% 71%',
'87': '121 64% 78%',
'90': '121 77% 82%',
'92': '120 86% 84%',
'94': '120 100% 86%',
'95': '118 100% 89%',
'96': '115 100% 91%',
'98': '108 100% 95%',
'99': '99 100% 97%',
'100': '0 0% 100%',
},
info: {
'0': '0 0% 0%',
'4': '221 100% 8%',
'5': '219 100% 9%',
'6': '218 100% 10%',
'10': '216 100% 13%',
'12': '215 100% 14%',
'14': '215 100% 16%',
'17': '214 100% 18%',
'20': '214 100% 21%',
'22': '214 100% 22%',
'24': '214 100% 24%',
'30': '213 100% 29%',
'40': '212 100% 38%',
'50': '216 79% 52%',
'60': '218 100% 65%',
'70': '220 100% 75%',
'80': '222 100% 84%',
'87': '223 100% 90%',
'90': '225 100% 92%',
'92': '225 100% 94%',
'94': '229 100% 96%',
'95': '230 100% 96%',
'96': '231 100% 97%',
'98': '240 100% 99%',
'99': '285 100% 99%',
'100': '0 0% 100%',
},
warning: {
'0': '0 0% 0%',
'4': '22 100% 5%',
'5': '23 100% 6%',
'6': '24 100% 7%',
'10': '27 100% 9%',
'12': '27 100% 10%',
'14': '28 100% 12%',
'17': '29 100% 13%',
'20': '30 100% 15%',
'22': '30 100% 16%',
'24': '31 100% 18%',
'30': '31 100% 22%',
'40': '32 100% 28%',
'50': '32 100% 35%',
'60': '32 94% 44%',
'70': '30 95% 57%',
'80': '27 100% 75%',
'87': '25 100% 84%',
'90': '25 100% 88%',
'92': '24 100% 91%',
'94': '24 100% 93%',
'95': '21 100% 95%',
'96': '22 100% 96%',
'98': '18 100% 98%',
'99': '300 100% 99%',
'100': '0 0% 100%',
},
},
},
transitionTimingFunction: {
'in-expo': 'cubic-bezier(.2, 0, 0, 1)', // Optional. Only necessary if using Variable Icon Fonts
},
keyframes: {
'accordion-down': {
from: { height: '0' },
to: { height: 'var(--radix-accordion-content-height)' },
},
'accordion-up': {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: '0' },
},
'collapsible-down': {
from: { height: '128px' },
to: { height: 'var(--radix-collapsible-content-height)' },
},
'collapsible-up': {
from: { height: 'var(--radix-collapsible-content-height)' },
to: { height: '128px' },
},
'grown-up': {
'0%': { opacity: '0', transform: 'scaleY(0%)' },
'60%': { opacity: '1', transform: 'scaleY(130%)' },
'100%': { opacity: '1', transform: 'scaleY(100%)' },
},
'linear-progress': {
'0%': { transform: 'translateX(-100%)' },
'50%': { transform: 'translateX(0%)' },
'100%': { transform: 'translateX(100%)' },
},
'circular-progress': {
'0%': {
'stroke-dasharray': '1px, 200px',
'stroke-dashoffset': '0px',
},
'50%': {
'stroke-dasharray': '100px, 200px',
'stroke-dashoffset': '-15px',
},
'100%': {
'stroke-dasharray': '100px, 200px',
'stroke-dashoffset': '-86px',
},
},
'progress-spin': {
'0%': {
transform: 'rotate(0deg)',
},
'100%': {
transform: 'rotate(360deg)',
},
},
'zoom-in-x': {
'0%': { opacity: '0', transform: 'scaleX(5%)' },
'100%': { opacity: '1', transform: 'scaleX(100%)' },
},
'zoom-in-y': {
'0%': { opacity: '0', transform: 'scaleY(5%)' },
'100%': { opacity: '1', transform: 'scaleY(100%)' },
},
'fade-in': {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
'collapsible-up': 'collapsible-up 0.2s ease-out',
'collapsible-down': 'collapsible-down 0.2s ease-out',
'grown-up': 'grown-up 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275)',
'zoom-in-x': 'zoom-in-x 0.25s cubic-bezier(.2,0,0,1)',
'zoom-in-y': 'zoom-in-y 0.25s cubic-bezier(.2,0,0,1)',
'linear-progress':
'linear-progress 2s infinite cubic-bezier(0.22, 0.61, 0.36, 1)',
'circular-progress': 'circular-progress 1.4s infinite ease-in-out',
'progress-spin': 'progress-spin 2s infinite linear',
'snackbar-in':
'zoom-in-y 0.25s cubic-bezier(.2,0,0,1), fade-in 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275)',
},
},
} satisfies OptionalConfig['theme']
export default theme
As you can see in the theme object, I have used the font Outfit Variable because I think it is pretty similar to the one Google uses. You can install it in your project with the following command, or remove it from the object if you don’t want to use custom font:
npm install @fontsource-variable/outfit
Now you can modify your tailwind.config.ts
to include the custom theme:
import type { Config } from 'tailwindcss'
import theme from './src/config/theme.ts'
const config = {
// ...
theme,
// ...
} satisfies Config
export default config
base.css
Modify the base.css file to include the following color variables and utilities:
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
@layer base {
:root {
--font-symbols: 'Material Symbols Rounded';
--primary: theme(colors.palette.primary.40);
--onPrimary: theme(colors.palette.primary.100);
--primaryContainer: theme(colors.palette.primary.90);
--onPrimaryContainer: theme(colors.palette.primary.10);
--secondary: theme(colors.palette.secondary.40);
--onSecondary: theme(colors.palette.secondary.100);
--secondaryContainer: theme(colors.palette.secondary.90);
--onSecondaryContainer: theme(colors.palette.secondary.10);
--tertiary: theme(colors.palette.tertiary.40);
--onTertiary: theme(colors.palette.tertiary.100);
--tertiaryContainer: theme(colors.palette.tertiary.90);
--onTertiaryContainer: theme(colors.palette.tertiary.10);
--error: theme(colors.palette.error.40);
--onError: theme(colors.palette.error.100);
--errorContainer: theme(colors.palette.error.90);
--onErrorContainer: theme(colors.palette.error.10);
--background: theme(colors.palette.neutral.98);
--onBackground: theme(colors.palette.neutral.10);
--surface: theme(colors.palette.neutral.98);
--onSurface: theme(colors.palette.neutral.10);
--surfaceVariant: theme(colors.palette.neutralVariant.90);
--onSurfaceVariant: theme(colors.palette.neutralVariant.30);
--outline: theme(colors.palette.neutralVariant.50);
--outlineVariant: theme(colors.palette.neutralVariant.80);
--shadow: theme(colors.palette.neutral.0);
--scrim: theme(colors.palette.neutral.0);
--inverseSurface: theme(colors.palette.neutral.20);
--inverseOnSurface: theme(colors.palette.neutral.95);
--inversePrimary: theme(colors.palette.primary.80);
--surfaceDim: theme(colors.palette.neutral.87);
--surfaceBright: theme(colors.palette.neutral.98);
--surfaceContainerLowest: theme(colors.palette.neutral.100);
--surfaceContainerLow: theme(colors.palette.neutral.96);
--surfaceContainer: theme(colors.palette.neutral.94);
--surfaceContainerHigh: theme(colors.palette.neutral.92);
--surfaceContainerHighest: theme(colors.palette.neutral.90);
--success: theme(colors.palette.success.40);
--onSuccess: theme(colors.palette.success.100);
--successContainer: theme(colors.palette.success.90);
--onSuccessContainer: theme(colors.palette.success.10);
--info: theme(colors.palette.info.40);
--onInfo: theme(colors.palette.info.100);
--infoContainer: theme(colors.palette.info.90);
--onInfoContainer: theme(colors.palette.info.10);
--warning: theme(colors.palette.warning.40);
--onWarning: theme(colors.palette.warning.100);
--warningContainer: theme(colors.palette.warning.90);
--onWarningContainer: theme(colors.palette.warning.10);
--primaryFixed: theme(colors.palette.primary.90);
--primaryFixedDim: theme(colors.palette.primary.80);
--onPrimaryFixed: theme(colors.palette.primary.10);
--onPrimaryFixedVariant: theme(colors.palette.primary.30);
--secondaryFixed: theme(colors.palette.secondary.90);
--secondaryFixedDim: theme(colors.palette.secondary.80);
--onSecondaryFixed: theme(colors.palette.secondary.10);
--onSecondaryFixedVariant: theme(colors.palette.secondary.30);
--tertiaryFixed: theme(colors.palette.tertiary.90);
--tertiaryFixedDim: theme(colors.palette.tertiary.80);
--onTertiaryFixed: theme(colors.palette.tertiary.10);
--onTertiaryFixedVariant: theme(colors.palette.tertiary.30);
--radius: 12px;
}
.dark {
--primary: theme(colors.palette.primary.80);
--onPrimary: theme(colors.palette.primary.20);
--primaryContainer: theme(colors.palette.primary.30);
--onPrimaryContainer: theme(colors.palette.primary.90);
--secondary: theme(colors.palette.secondary.80);
--onSecondary: theme(colors.palette.secondary.20);
--secondaryContainer: theme(colors.palette.secondary.30);
--onSecondaryContainer: theme(colors.palette.secondary.90);
--tertiary: theme(colors.palette.tertiary.80);
--onTertiary: theme(colors.palette.tertiary.20);
--tertiaryContainer: theme(colors.palette.tertiary.30);
--onTertiaryContainer: theme(colors.palette.tertiary.90);
--error: theme(colors.palette.error.80);
--onError: theme(colors.palette.error.20);
--errorContainer: theme(colors.palette.error.30);
--onErrorContainer: theme(colors.palette.error.90);
--background: theme(colors.palette.neutral.6);
--onBackground: theme(colors.palette.neutral.90);
--surface: theme(colors.palette.neutral.6);
--onSurface: theme(colors.palette.neutral.90);
--surfaceVariant: theme(colors.palette.neutralVariant.30);
--onSurfaceVariant: theme(colors.palette.neutralVariant.80);
--outline: theme(colors.palette.neutralVariant.60);
--outlineVariant: theme(colors.palette.neutralVariant.30);
--shadow: theme(colors.palette.neutral.0);
--scrim: theme(colors.palette.neutral.0);
--inverseSurface: theme(colors.palette.neutral.90);
--inverseOnSurface: theme(colors.palette.neutral.20);
--inversePrimary: theme(colors.palette.primary.40);
--surfaceDim: theme(colors.palette.neutral.6);
--surfaceBright: theme(colors.palette.neutral.24);
--surfaceContainerLowest: theme(colors.palette.neutral.4);
--surfaceContainerLow: theme(colors.palette.neutral.10);
--surfaceContainer: theme(colors.palette.neutral.12);
--surfaceContainerHigh: theme(colors.palette.neutral.17);
--surfaceContainerHighest: theme(colors.palette.neutral.22);
--success: theme(colors.palette.success.80);
--onSuccess: theme(colors.palette.success.20);
--successContainer: theme(colors.palette.success.30);
--onSuccessContainer: theme(colors.palette.success.90);
--info: theme(colors.palette.info.80);
--onInfo: theme(colors.palette.info.20);
--infoContainer: theme(colors.palette.info.30);
--onInfoContainer: theme(colors.palette.info.90);
--warning: theme(colors.palette.warning.80);
--onWarning: theme(colors.palette.warning.20);
--warningContainer: theme(colors.palette.warning.30);
--onWarningContainer: theme(colors.palette.warning.90);
}
* {
border-color: theme(colors.outline);
min-width: 0;
}
body {
font-feature-settings:
'rlig' 1,
'calt' 1;
}
i {
font-family: var(--font-symbols);
font-style: normal;
line-height: 1;
letter-spacing: normal;
text-transform: none;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-feature-settings: 'liga';
user-select: none;
background-repeat: no-repeat;
display: inline-block;
overflow: hidden;
}
input[type='search']::-webkit-search-decoration,
input[type='search']::-webkit-search-cancel-button,
input[type='search']::-webkit-search-results-button,
input[type='search']::-webkit-search-results-decoration {
display: none;
}
}
@layer utilities {
.font-regular {
font-variation-settings:
'GRAD' 0,
'FILL' 0;
}
.font-emphasis {
font-variation-settings:
'GRAD' 200,
'FILL' 0;
}
.font-filled {
font-variation-settings:
'GRAD' 0,
'FILL' 1;
}
.font-filled-emphasis {
font-variation-settings:
'GRAD' 200,
'FILL' 1;
}
}
Root layout
In your root layout import the styles, and the fonts.
import '@fontsource-variable/outfit'
import '@/styles/base.css'
//...
Animations
For the animations install tailwindcss-animate
plugin and add it to your tailwind.config.ts
npm install tailwindcss-animate
tailwind.config.ts
// ...
import TailwindAnimate from 'tailwindcss-animate'
const config = {
//...
plugins: [
TailwindAnimate,
// ...
],
} satisfies Config
export default config
Icons
We will be using the Material Symbols for the icons.
npm install material-symbols
In your root layout
import the Material Symbols.
// ...
import 'material-symbols/rounded.css'
//...
And create the component Icon in src/components/ui/icon.tsx
import * as React from 'react'
import { cn } from '@/lib/utils'
/**
* The symbols names always goes in minus case and separated by "_".
* For example: "Add circle" will be "add_circle"; Home, will be "home"
*/
interface IconProps extends React.HTMLAttributes<HTMLDivElement> {
symbol: string // You can find the symbols names here: https://fonts.google.com/icons?icon.style=Rounded
}
const Icon = React.forwardRef<HTMLDivElement, IconProps>(
({ symbol, className, ...props }, ref) => (
<i
ref={ref}
className={cn(
'font-regular text-2xl leading-none transition-[font-variation-settings] duration-300 ease-in-expo',
className
)}
{...props}
>
{symbol}
</i>
)
)
Icon.displayName = 'Icon'
export { Icon }
Now you will be able to use it in your components as:
import { Icon } from '@/components/ui/icon'
//...
<Icon symbol="home" />
//...
Utils
Finally create the file src/lib/utils.ts
and add the following code:
import { clsx } from 'clsx'
import type { ClassValue } from 'clsx'
import { extendTailwindMerge } from 'tailwind-merge'
const tailwindMergeConfig = {
classGroups: {
'font-size': [
{
text: [
'display-lg',
'display-md',
'display-sm',
'headline-lg',
'headline-md',
'headline-sm',
'title-lg',
'title-md',
'title-sm',
'label-lg',
'label-md',
'label-sm',
'body-lg',
'body-md',
'body-sm',
],
},
],
},
}
export const twMerge = extendTailwindMerge({
extend: {
...tailwindMergeConfig,
},
})
export const cn = (...inputs: ClassValue[]) => {
return twMerge(clsx(inputs))
}