Learn how to migrate your project to Shuriken UI v4.
Shuriken UI v4 now uses Tailwind CSS v4. This new version brings a lot of improvements and new features, but also some breaking changes.
We encourage you to first read the Tailwind upgrade: https://tailwindcss.com/docs/upgrade-guide
With the current Tailwind v3 version, the tailwind.config.js
file allows you to override Tailwind’s default design tokens. It’s a customization hub where you can add custom utility classes and themes, disable plugins, and more.
import { withShurikenUI } from '@shuriken-ui/tailwind'
import colors from 'tailwindcss/colors'
export default withShurikenUI({
theme: {
fontFamilly: {
sans: ['Inter', 'sans-serif'],
alt: ['Roboto', 'sans-serif'],
},
extend: {
colors: {
'primary': colors.violet,
'primary-invert': colors.white,
},
},
},
})
In Tailwind v4, everything is CSS. You’ll only need to import tailwindcss into your main CSS file, and you’re good to go. We decided to follow the same approach in Shuriken UI v4 to integrate seamlessly with the new Tailwind CSS paradigm. Therefore, same result can be obtained by using the following CSS file:
@import 'tailwindcss';
@import '@shuriken-ui/nuxt';
@theme {
--font-sans: Inter, sans-serif;
--font-alt: Roboto, sans-serif;
--color-primary-50: var(--color-violet-50);
--color-primary-100: var(--color-violet-100);
--color-primary-200: var(--color-violet-200);
--color-primary-300: var(--color-violet-300);
--color-primary-400: var(--color-violet-400);
--color-primary-500: var(--color-violet-500);
--color-primary-600: var(--color-violet-600);
--color-primary-700: var(--color-violet-700);
--color-primary-800: var(--color-violet-800);
--color-primary-900: var(--color-violet-900);
--color-primary-950: var(--color-violet-950);
--color-primary-invert: var(--color-white);
}
With this new CSS variables approach, Tailwind v4 is able to infer color classes if your variables start with the --color-*
prefix. If you create a --color-custom
variable, Tailwind will automatically generate classes like bg-custom
or text-custom
.
You can learn more about theming in Shuriken UI by reading the theming documentation overview.
All the improvements brought by the new version of Shuriken UI come with a cost. We had to make some breaking changes to the library to align with the new Tailwind CSS v4, as well as rewrite most components from the ground using Reka UI. Here are the most important changes you need to be aware of.
We used to serve Shuriken UI as a Nuxt layer. However, for performance and usability reasons, we chose to serve shuriken UI as a standard Nuxt module. This means that you will have to install Shuriken UI as a Nuxt module in your project. You will also need to import your main CSS file in your nuxt.config.js
file:
export default defineNuxtConfig({
extends: [
'@shuriken-ui/nuxt', ],
modules: [
'@shuriken-ui/nuxt', ],
css: [
'~/assets/css/main.css', ],
})
We completely removed @shuriken-ui/tailwind
package. We switched to pure Tailwind utility classes used in component templates, greatly improving performance, tree shaking, final bundle size and the overall developer experience.
Therefore, utils has been cleaned to only keep nui-focus
, nui-mask
, nui-slimscroll
nui-focus
now need to have a state prefix like focus:
, focus-within:
, focus-visible:
to work.
We've been eyeing Radix UI for a while, and we were impressed by the way they were building their components as primitives focusing on accessibility first. Unfortunately, Radix is for React, and there were no Vue alternatives, besides some discontinued projects.
It was until we stumbled upon Reka UI (formerly Radix Vue), a Vue port of Radix UI, that we decided to give it a try. The results exceeded our expectations, and we decided to use Reka UI as the foundation of our component system.
We rewrote all the components using Reka UI, a new library that acts as the Vue port of Radix UI. Reka UI is a library that allows you to create UI components using their unstyled and accessible primitives.
These low level components are extremely well implemented and accessible by default. We used them as the foundation of the Shuriken UI component system. This means that all the components have been rewritten from scratch, and some of them have been removed or renamed.
You should be familiar with Reka UI and its concepts before using Shuriken UI. If not, you should go through the Reka UI documentation.
In previous version, we relied on complex components with hard to understand structure.
<template>
<BaseAccordion
:classes="{
wrapper: 'bg-white rounded-lg shadow-sm',
summary: 'p-4',
}"
:items
/>
</template>
<script setup lang="ts">
const items = [
{
title: 'Accordion Item 1',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quo tandem modo? Inde igitur, inquit, ordiendum est. Primum quid tu dicis breve? Duo Reges: constructio interrete.',
},
{
title: 'Accordion Item 2',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quo tandem modo? Inde igitur, inquit, ordiendum est. Primum quid tu dicis breve? Duo Reges: constructio interrete.',
},
{
title: 'Accordion Item 3',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quo tandem modo? Inde igitur, inquit, ordiendum est. Primum quid tu dicis breve? Duo Reges: constructio interrete.',
},
]
</script>
Building on top of Reka made us switch the way we build components. Rather than building monolithic components, we decided to split them into smaller parts, making them more flexible and easier to maintain.
This approach reduce the nodes in the component tree, reducing the need of using classes
property to style the components.
In addition, most components inherit from Reka UI components, making them more accessible and easier to use.
Here is how the BaseAccordion
component is used in the new version:
<template>
<BaseAccordion class="bg-white rounded-lg shadow-sm">
<BaseAccordionItem
v-for="item in items"
:key="item.value"
:value="item.value"
class="p-4"
>
<template #title>
{{ item.title }}
</template>
{{ item.content }}
</BaseAccordionItem>
</BaseAccordion>
</template>
<script setup lang="ts">
const items = [
{
value: 'accordion-item-1',
title: 'Accordion Item 1',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quo tandem modo? Inde igitur, inquit, ordiendum est. Primum quid tu dicis breve? Duo Reges: constructio interrete.',
},
{
value: 'accordion-item-2',
title: 'Accordion Item 2',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quo tandem modo? Inde igitur, inquit, ordiendum est. Primum quid tu dicis breve? Duo Reges: constructio interrete.',
},
{
value: 'accordion-item-3',
title: 'Accordion Item 3',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quo tandem modo? Inde igitur, inquit, ordiendum est. Primum quid tu dicis breve? Duo Reges: constructio interrete.',
},
]
</script>
In previous version, inputs components such as BaseInput
, BaseInputNumber
, BaseTextarea
, BaseSelect
, ... were complex components with a lot of props and options to render labels, errors, etc...
<script setup lang="ts">
const value = ref('')
</script>
<template>
<BaseInput
v-model="value"
type="text"
label="Search records..."
placeholder="Type something..."
rounded="md"
loading
icon="lucide:search"
/>
</template>
We decided to simplify them to render only the input itself, and let the developer handle the rest.
We created BaseProviderField
components that are used to wrap the input and its label, error, etc... This allow to create higher components such as BaseField
:
<script setup lang="ts">
const value = ref('')
</script>
<template>
<BaseField
v-slot="{ inputRef, inputAttrs }"
label="Search records..."
state="loading"
>
<div class="focus-within:nui-focus rounded-md flex *:rounded-none *:border-e-0 *:first:rounded-s-md *:last:border-e *:last:rounded-e-md">
<BaseIconBox size="sm">
<Icon name="lucide:search" />
</BaseIconBox>
<BaseInput
:ref="inputRef"
v-model="value"
v-bind="inputAttrs"
placeholder="Type something..."
class="focus-visible:ring-0 border-s-0 ps-0"
/>
</div>
</BaseField>
</template>
BaseCheckbox
does not accept arrays as model value anymore. You should wrap them in a BaseCheckboxGroup
component.BaseRadio
does not accept model value anymore. You should wrap them in a BaseRadioGroup
component.We made some changes to the props of the components to make them more consistent and easier to use. We also removed some props that were not necessary anymore.
We worked to make styling options and props more consistent, and removed a lot of them, while giving you more control over the components.
color
props have been renamed to variant
.variant
props have been removed.contrast
props have been removed in favor of css variables.shadow
props have been removed in favor of tailwind classes.We removed some components that were not necessary anymore, or that were not used by the community. Here is the list of the removed components:
ButtonGroup
ButtonIcon
ButtonAction
ButtonClose
CheckboxAnimated
FocusLoop
HeadlessCheckbox
HeadlessRadio
TabSlider
Listbox
TreeSelect
At some point, all the components have been reworked to be more consistent and easier to use. However, there are some components that received significant changes, and now have a very different structure. Make sure to check how they work now:
Button
Autocomplete
Select
InputNumber
Input
InputFile
Textarea
Progress
ProgressCircle
Tabs
For more details about all the changes, you can review the v4 changelog.