As my project contains 2 tauri apps with React as frontend, I decided to create an external react components library I will import in each app.
The main problem I have is I'm using tailwindcss 4 with both apps ans the lib. But, When I import components from the lib, in app, they are not correctly styled.
My workspace looks like this (it's a nestjs workspace in which I've added tauri apps)
| - apps
| | - admin-app
| | - client-app
| - libs
| | - components
My libs/components/vite.config.ts
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [tailwindcss(), react()],
build: {
lib: {
entry: {
'components/index': resolve(__dirname, 'src/components/index.ts'),
'contexts/index': resolve(__dirname, 'src/contexts/index.ts'),
'helpers/index': resolve(__dirname, 'src/helpers/index.ts'),
'hooks/index': resolve(__dirname, 'src/hooks/index.ts'),
},
formats: ['es', 'cjs'],
fileName: (format, entryName) => {
return `${entryName}.${format === 'es' ? 'es' : 'cjs'}.js`;
},
},
cssCodeSplit: false,
rollupOptions: {
external: ['react', 'react-dom', 'react/jsx-runtime'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
},
},
sourcemap: true,
emptyOutDir: true,
},
});
My admin-app/vite.config.ts
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import { defineConfig } from 'vite';
const host = process.env.TAURI_DEV_HOST;
// https://vitejs.dev/config/
export default defineConfig(async () => ({
plugins: [tailwindcss(), react()],
resolve: {
alias: {
'@app/client': path.resolve(__dirname, '../../libs/client/src'),
'@app/components': path.resolve(__dirname, '../../libs/components/src'),
'@app/interfaces': path.resolve(__dirname, '../../libs/interfaces/src'),
},
},
clearScreen: false,
server: {
fs: {
allow: ['..', '../../libs'],
},
port: 1420,
strictPort: true,
host: host || false,
hmr: host
? {
protocol: 'ws',
host,
port: 1421,
}
: undefined,
watch: {
ignored: ['**/src-tauri/**'],
},
},
}));
And finally a form where inputs are imported from libs/components
import { Schema } from '@app/client';
import { SubmitButton, TextInput } from '@app/components/components';
import { FormProvider } from 'react-hook-form';
import { useCustomForm } from '../../hooks/useCustomForm';
import { IFormProps } from '../../ts/Form';
import { defaultProductCreateValues } from '../../ts/Values';
interface IProductFormProps extends IFormProps<typeof Schema.ProductCreate> {}
export const ProductForm = ({
values: val,
onCancel,
onSubmit,
}: IProductFormProps) => {
const { methods, values, isSubmitable } = useCustomForm({
defaultValues: defaultProductCreateValues(val),
schema: Schema.ProductCreate,
});
return (
<FormProvider {...methods}>
<form
className="grid grid-cols-6 gap-y-4"
onSubmit={methods.handleSubmit(onSubmit)}
>
<TextInput
className="col-span-6"
value={values['name']}
name="name"
label="Nom"
register={methods.register}
type="text"
/>
<SubmitButton
text="Valider"
disabled={!isSubmitable()}
onCancel={onCancel}
/>
</form>
</FormProvider>
);
};
SubmitButton.tsx
import { Fragment } from 'react';
import { classNames } from '../../helpers/style';
import '../../style.css';
interface ISubmitButtonProps {
cancelDisabled?: boolean;
disabled?: boolean;
text?: string;
fullSize?: boolean;
onCancel?: () => void;
onValidate?: () => void;
}
export const SubmitButton = ({
text,
cancelDisabled,
disabled = false,
fullSize = false,
onCancel,
onValidate,
}: ISubmitButtonProps) => {
return (
<Fragment>
{onCancel ? (
<div
className={classNames(
fullSize ? 'justify-between' : 'justify-end',
'flex gap-x-4 col-span-6',
)}
>
<button
disabled={disabled}
type="submit"
onClick={onValidate}
className={classNames(
fullSize && 'flex-1',
'bg-green-600 hover:bg-green-500 disabled:bg-gray-800 disabled:text-gray-500 disabled:border disabled:border-gray-900 py-2 px-4 rounded-md',
)}
>
Valider
</button>
{onCancel && (
<button
type="button"
disabled={cancelDisabled}
className={classNames(
fullSize && '',
'bg-red-600 hover:bg-red-500 disabled:bg-gray-800 disabled:text-gray-500 disabled:border disabled:border-gray-900 py-2 px-4 rounded-md',
)}
onClick={onCancel}
>
Annuler
</button>
)}
</div>
) : (
<button
disabled={disabled}
className="bg-blue-500 text-gray-200 disabled:bg-gray-800 disabled:text-gray-500 col-span-6 uppercase py-2 mt-4 rounded-md font-semibold"
>
{text}
</button>
)}
</Fragment>
);
};
As you can see, there are colors defined for the buttons but, the result looks like this.
PS: components lib style is applied in admin-app/App.tsx
import { DialogArea, NotificationArea } from '@app/components/components';
import { RouterProvider } from 'react-router-dom';
import { router } from './routes';
import '@app/components/style.css';
import './assets/styles/style.css';
function App() {
return (
<DialogArea>
<NotificationArea>
<RouterProvider router={router} />
</NotificationArea>
</DialogArea>
);
}
export default App;
Although you haven't shared your TailwindCSS configuration, I believe your issue is related to source declaration.
Tailwind v4 introduced automatic source detection. With this, you no longer need to manually declare the paths to your source files and folders where class names are used. However, to save resources, automatic source detection ignores paths listed in .gitignore
, as well as the node_modules
directory. Understandably, it also doesn't include any sources located outside the project root by default.
The CSS-first configuration - which is now the only supported method as of v4 - provides a @source
CSS directive that allows you to expand or restrict the list of source files. In your case, you need to expand it by adding the src
folders from your external libs
directory like this:
@source
directive - TailwindCSS v4 Docs./style.css
@import "tailwindcss";
@source "./../../libs/client/src";
@source "./../../libs/components/src";
@source "./../../libs/interfaces/src";
Important: Note that you need to use a relative path from your main CSS file. Only include paths that actually contain files where TailwindCSS classes might be used.