diff --git a/packages/twenty-apps/fixtures/minimal-app/css.d.ts b/packages/twenty-apps/fixtures/minimal-app/css.d.ts
new file mode 100644
index 0000000000..35306c6fc9
--- /dev/null
+++ b/packages/twenty-apps/fixtures/minimal-app/css.d.ts
@@ -0,0 +1 @@
+declare module '*.css';
diff --git a/packages/twenty-apps/fixtures/minimal-app/my.front-component.tsx b/packages/twenty-apps/fixtures/minimal-app/my.front-component.tsx
index 532e48ff7d..b6bb9ac048 100644
--- a/packages/twenty-apps/fixtures/minimal-app/my.front-component.tsx
+++ b/packages/twenty-apps/fixtures/minimal-app/my.front-component.tsx
@@ -1,5 +1,7 @@
import { defineFrontComponent } from 'twenty-sdk/define';
+import 'twenty-ui/style.css';
+
export const MyComponent = () => {
return (
diff --git a/packages/twenty-apps/fixtures/minimal-app/package.json b/packages/twenty-apps/fixtures/minimal-app/package.json
index 8e04d0a7be..49d6c43036 100644
--- a/packages/twenty-apps/fixtures/minimal-app/package.json
+++ b/packages/twenty-apps/fixtures/minimal-app/package.json
@@ -15,7 +15,8 @@
},
"dependencies": {
"twenty-sdk": "latest",
- "twenty-client-sdk": "latest"
+ "twenty-client-sdk": "latest",
+ "twenty-ui": "latest"
},
"devDependencies": {
"typescript": "^5.9.3",
diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/__tests__/css-injection-plugin.spec.ts b/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/__tests__/css-injection-plugin.spec.ts
new file mode 100644
index 0000000000..df5fabecd2
--- /dev/null
+++ b/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/__tests__/css-injection-plugin.spec.ts
@@ -0,0 +1,33 @@
+import { mkdtemp, readFile, rm } from 'node:fs/promises';
+import { tmpdir } from 'node:os';
+import { join } from 'path';
+
+import * as esbuild from 'esbuild';
+
+import { MINIMAL_APP_PATH } from '@/cli/__tests__/apps/fixture-paths';
+import { getBaseFrontComponentBuildOptions } from '@/cli/utilities/build/common/front-component-build/utils/get-base-front-component-build-options';
+
+describe('front-component build CSS injection', () => {
+ it('inlines an imported twenty-ui/style.css as a runtime document.head style injection', async () => {
+ const outputDir = await mkdtemp(join(tmpdir(), 'css-injection-'));
+
+ try {
+ await esbuild.build({
+ ...getBaseFrontComponentBuildOptions(),
+ entryPoints: [join(MINIMAL_APP_PATH, 'my.front-component.tsx')],
+ outdir: outputDir,
+ });
+
+ const output = await readFile(
+ join(outputDir, 'my.front-component.mjs'),
+ 'utf-8',
+ );
+
+ expect(output).toContain('document.createElement("style")');
+ expect(output).toContain('document.head.appendChild');
+ expect(output).toContain('box-sizing');
+ } finally {
+ await rm(outputDir, { recursive: true, force: true });
+ }
+ }, 30000);
+});
diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/css-injection-plugin.ts b/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/css-injection-plugin.ts
new file mode 100644
index 0000000000..79c3d53ff9
--- /dev/null
+++ b/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/css-injection-plugin.ts
@@ -0,0 +1,24 @@
+import { readFile } from 'node:fs/promises';
+
+import type * as esbuild from 'esbuild';
+
+const buildStyleInjectionModule = (cssText: string): string =>
+ `if (typeof document !== 'undefined') {
+ const styleElement = document.createElement('style');
+ document.head.appendChild(styleElement);
+ styleElement.textContent = ${JSON.stringify(cssText)};
+}`;
+
+export const cssInjectionPlugin: esbuild.Plugin = {
+ name: 'css-injection',
+ setup: (build) => {
+ build.onLoad({ filter: /\.css$/ }, async ({ path }) => {
+ const cssText = await readFile(path, 'utf8');
+
+ return {
+ contents: buildStyleInjectionModule(cssText),
+ loader: 'js',
+ };
+ });
+ },
+};
diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/utils/get-front-component-build-plugins.ts b/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/utils/get-front-component-build-plugins.ts
index e00de62080..e93eaad622 100644
--- a/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/utils/get-front-component-build-plugins.ts
+++ b/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/utils/get-front-component-build-plugins.ts
@@ -1,5 +1,6 @@
import type * as esbuild from 'esbuild';
+import { cssInjectionPlugin } from '../css-injection-plugin';
import { createJsxRuntimeRemoteWrapperPlugin } from '../jsx-runtime-remote-wrapper-plugin';
import { jsxTransformToRemoteDomWorkerFormatPlugin } from '../jsx-transform-to-remote-dom-worker-format-plugin';
import { createPreactAliasPlugin } from '../preact-alias-plugin';
@@ -17,5 +18,6 @@ export const getFrontComponentBuildPlugins = (
),
...(options?.usePreact ? [createPreactAliasPlugin()] : []),
jsxTransformToRemoteDomWorkerFormatPlugin,
+ cssInjectionPlugin,
stripCommentsPlugin,
];
diff --git a/packages/twenty-sdk/src/cli/utilities/build/manifest/__tests__/manifest-extract-front-component-twenty-ui-css.spec.ts b/packages/twenty-sdk/src/cli/utilities/build/manifest/__tests__/manifest-extract-front-component-twenty-ui-css.spec.ts
new file mode 100644
index 0000000000..a9c6039fdb
--- /dev/null
+++ b/packages/twenty-sdk/src/cli/utilities/build/manifest/__tests__/manifest-extract-front-component-twenty-ui-css.spec.ts
@@ -0,0 +1,22 @@
+import { join } from 'path';
+
+import { MINIMAL_APP_PATH } from '@/cli/__tests__/apps/fixture-paths';
+import { extractManifestFromFile } from '@/cli/utilities/build/manifest/manifest-extract-config-from-file';
+import { type FrontComponentConfig } from '@/sdk/define/front-component/front-component-config';
+
+describe('extractManifestFromFile - front component importing twenty-ui', () => {
+ it('extracts the config from a front component that side-effect imports twenty-ui/style.css', async () => {
+ const filePath = join(MINIMAL_APP_PATH, 'my.front-component.tsx');
+
+ const result = await extractManifestFromFile({
+ appPath: MINIMAL_APP_PATH,
+ filePath,
+ });
+
+ expect(result.errors).toEqual([]);
+ expect(result.config.universalIdentifier).toBe(
+ 'e1e2e3e4-e5e6-4000-8000-000000000020',
+ );
+ expect(result.config.name).toBe('my-component');
+ }, 30000);
+});
diff --git a/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-extract-config-from-file.ts b/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-extract-config-from-file.ts
index bf547162fc..04e935f0c9 100644
--- a/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-extract-config-from-file.ts
+++ b/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-extract-config-from-file.ts
@@ -83,6 +83,7 @@ const loadModule = async ({
target: 'node18',
jsx: 'automatic',
tsconfig: hasTsconfig ? tsconfigPath : undefined,
+ loader: { '.css': 'empty' },
alias: {
...(reactPath && { react: reactPath }),
...(reactDomPath && { 'react-dom': reactDomPath }),