---
title: Компоненты фронтенда
description: Создавайте компоненты React, которые отображаются внутри интерфейса Twenty в изолированной песочнице.
icon: window-maximize
---

Фронтенд-компоненты — это компоненты React, которые отображаются непосредственно внутри интерфейса Twenty. Они выполняются в изолированном Web Worker с использованием Remote DOM — ваш код изолирован (sandboxed), но рендерится нативно на странице, а не в iframe.

## Где можно использовать фронт-компоненты

Фронт-компоненты могут отображаться в двух местах внутри Twenty:

* **Боковая панель** — фронт-компоненты с интерфейсом открываются в правой боковой панели. Это поведение по умолчанию, когда фронт-компонент запускается из меню команд.
* **Виджеты (дашборды и страницы записей)** — фронт-компоненты можно встраивать как виджеты в [макеты страниц](/l/ru/developers/extend/apps/layout/page-layouts). При настройке дашборда или макета страницы записи пользователи могут добавить виджет фронт-компонента.

Сам по себе фронт-компонент недоступен из интерфейса — его нужно *сделать доступным*. Сделать это можно двумя способами:

* **Связать его с [элементом командного меню](/l/ru/developers/extend/apps/layout/command-menu-items)** — регистрирует его в командном меню (Cmd+K) и, при необходимости, как закреплённое быстрое действие.
* **Встроить его как виджет в [макет страницы](/l/ru/developers/extend/apps/layout/page-layouts)** — размещает его на странице деталей записи или на дашборде.

## Простой пример

Самый быстрый способ увидеть фронт-компонент в действии — связать его с [`defineCommandMenuItem`](/l/ru/developers/extend/apps/layout/command-menu-items), чтобы он появился как кнопка быстрого действия в правом верхнем углу страницы:

```tsx src/front-components/hello-world.tsx
import { defineFrontComponent } from 'twenty-sdk/define';

const HelloWorld = () => {
  return (
    <div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
      <h1>Hello from my app!</h1>
      <p>This component renders inside Twenty.</p>
    </div>
  );
};

export default defineFrontComponent({
  universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
  name: 'hello-world',
  description: 'A simple front component',
  component: HelloWorld,
});
```

```ts src/command-menu-items/hello-world.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';

export default defineCommandMenuItem({
  universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
  shortLabel: 'Hello',
  label: 'Hello World',
  icon: 'IconBolt',
  isPinned: true,
  availabilityType: 'GLOBAL',
  frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
});
```

После синхронизации с помощью `yarn twenty dev` (или однократного запуска `yarn twenty dev --once`) быстрое действие появится в правом верхнем углу страницы:

<div style={{textAlign: 'center'}}>
  <img src="/images/docs/developers/extends/apps/quick-action.png" alt="Кнопка быстрого действия в правом верхнем углу" />
</div>

Нажмите её, чтобы отобразить компонент инлайн.

## Поля конфигурации

| Поле                  | Обязательно | Описание                                                                                           |
| --------------------- | ----------- | -------------------------------------------------------------------------------------------------- |
| `universalIdentifier` | Да          | Стабильный уникальный идентификатор для этого компонента                                           |
| `component`           | Да          | Функция компонента React                                                                           |
| `name`                | Нет         | Отображаемое имя                                                                                   |
| `description`         | Нет         | Описание того, что делает компонент                                                                |
| `isHeadless`          | Нет         | Установите значение `true`, если у компонента нет видимого пользовательского интерфейса (см. ниже) |

## Размещение фронт-компонента на странице

Помимо команд, вы можете встроить фронт-компонент непосредственно на страницу записи, добавив его как виджет в **макет страницы**. См. [макеты страниц](/l/ru/developers/extend/apps/layout/page-layouts) для подробностей.

## Headless и non-headless

Фронт-компоненты поддерживают два режима отображения, управляемых опцией `isHeadless`:

**Non-headless (по умолчанию)** — компонент отображает видимый интерфейс. При запуске из меню команд он открывается в боковой панели. Это поведение по умолчанию, когда `isHeadless` имеет значение `false` или опущен.

**Headless (`isHeadless: true`)** — компонент монтируется невидимо в фоновом режиме. Он не открывает боковую панель. Компоненты headless предназначены для действий, которые выполняют логику и затем размонтируются — например, запуск асинхронной задачи, переход на страницу или показ модального окна подтверждения. Они естественно сочетаются с компонентами SDK Command, описанными ниже.

```tsx src/front-components/sync-tracker.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useEffect } from 'react';

const SyncTracker = () => {
  const recordId = useRecordId();

  useEffect(() => {
    enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' });
  }, [recordId]);

  return null;
};

export default defineFrontComponent({
  universalIdentifier: '...',
  name: 'sync-tracker',
  description: 'Tracks record views silently',
  isHeadless: true,
  component: SyncTracker,
});
```

Поскольку компонент возвращает `null`, Twenty пропускает рендеринг контейнера для него — в макете не появляется пустое место. Компонент по-прежнему имеет доступ ко всем хукам и API взаимодействия с хостом.

## Компоненты SDK Command

Пакет `twenty-sdk` предоставляет четыре вспомогательных компонента Command, предназначенных для headless фронт-компонентов. Каждый компонент выполняет действие при монтировании, обрабатывает ошибки, показывая уведомление snackbar, и автоматически размонтирует фронт-компонент по завершении.

Импортируйте их из `twenty-sdk/command`:

* **`Command`** — запускает асинхронный колбэк через проп `execute`.
* **`CommandLink`** — переходит по пути внутри приложения. Пропы: `to`, `params`, `queryParams`, `options`.
* **`CommandModal`** — открывает модальное окно подтверждения. Если пользователь подтвердит, выполняет колбэк `execute`. Пропы: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`.
* **`CommandOpenSidePanelPage`** — открывает страницу боковой панели. Пропсы зависят от `page` — например, `ViewRecord` принимает `recordId` + `objectNameSingular`, другие страницы принимают `pageTitle` + `pageIcon`.

Полный пример headless фронт-компонента, использующего `Command` для запуска действия из меню команд:

```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
import { CoreApiClient } from 'twenty-sdk/clients';

const RunAction = () => {
  const execute = async () => {
    const client = new CoreApiClient();

    await client.mutation({
      createTask: {
        __args: { data: { title: 'Created by my app' } },
        id: true,
      },
    });
  };

  return <Command execute={execute} />;
};

export default defineFrontComponent({
  universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
  name: 'run-action',
  description: 'Creates a task from the command menu',
  component: RunAction,
  isHeadless: true,
});
```

```ts src/command-menu-items/run-action.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';

export default defineCommandMenuItem({
  universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
  label: 'Run my action',
  icon: 'IconPlayerPlay',
  frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```

А также пример с использованием `CommandModal` для запроса подтверждения перед выполнением:

```tsx src/front-components/delete-draft.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { CommandModal } from 'twenty-sdk/command';

const DeleteDraft = () => {
  const execute = async () => {
    // perform the deletion
  };

  return (
    <CommandModal
      title="Delete draft?"
      subtitle="This action cannot be undone."
      execute={execute}
      confirmButtonText="Delete"
      confirmButtonAccent="danger"
    />
  );
};

export default defineFrontComponent({
  universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456',
  name: 'delete-draft',
  description: 'Deletes a draft with confirmation',
  component: DeleteDraft,
  isHeadless: true,
});
```

## Вызов логической функции

Front-компоненты выполняются в браузере в изолированном Web Worker, в то время как [логические функции](/l/ru/developers/extend/apps/logic/logic-functions) выполняются на стороне сервера. Между ними нет прямого внутрипроцессного вызова — вместо этого front-компонент обращается к логической функции по HTTP.

Логическая функция, объявленная с `httpRouteTriggerSettings`, доступна по HTTP по своему пути маршрута. Twenty внедряет в воркер базовый URL, с которого обслуживаются ваши функции, в виде `TWENTY_FUNCTIONS_URL` вместе с `TWENTY_APP_ACCESS_TOKEN`, который аутентифицирует вызов. Пока что нет отдельного клиентского SDK для вызова ваших собственных функций, поэтому вызывайте их с помощью обычного `fetch`:

> **В Twenty Cloud логические функции с HTTP-триггером обслуживаются на выделенном домене для каждого рабочего пространства** по адресу `https://\<your-workspace-subdomain>.twenty.com\<path>` — именно к этому и разрешается `TWENTY_FUNCTIONS_URL`. Для внешних вызовов скопируйте точный URL из настроек **HTTP trigger** функции или на вкладке **Settings** приложения.

<Warning>
  Устаревший маршрут функции `/s/` **не рекомендуется к использованию** и будет **деактивирован 2026-07-24**. Вместо этого используйте `TWENTY_FUNCTIONS_URL` (выше) и перенесите все жестко заданные URL вида `/s/` до этой даты. Маршрут `/s/` по-прежнему доступен при самостоятельном размещении (self-hosting).
</Warning>

Безголовый front-компонент может выполнить вызов при монтировании через компонент `Command`, а затем автоматически размонтироваться:

```tsx src/front-components/sync-prs.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';

const SyncPrs = () => {
  const execute = async () => {
    await fetch(`${process.env.TWENTY_FUNCTIONS_URL}/github/fetch-prs`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.TWENTY_APP_ACCESS_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ owner: 'twentyhq', repo: 'twenty' }),
    });
  };

  return <Command execute={execute} />;
};

export default defineFrontComponent({
  universalIdentifier: '...',
  name: 'sync-prs',
  description: 'Triggers the fetch-prs logic function',
  isHeadless: true,
  component: SyncPrs,
});
```

Путь, добавляемый к `TWENTY_FUNCTIONS_URL`, — это значение `httpRouteTriggerSettings.path` логической функции. Сохраните `isAuthRequired: true`; `TWENTY_APP_ACCESS_TOKEN`, который Twenty выпускает для вашего компонента, аутентифицирует запрос:

```ts src/logic-functions/fetch-prs.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/logic-function';

const handler = async (event: RoutePayload) => {
  const { owner, repo } = (event.body ?? {}) as { owner: string; repo: string };
  // ...fetch from GitHub and persist records...
  return { ok: true };
};

export default defineLogicFunction({
  universalIdentifier: '...',
  name: 'fetch-prs',
  handler,
  httpRouteTriggerSettings: {
    path: '/github/fetch-prs',
    httpMethod: 'POST',
    isAuthRequired: true,
  },
});
```

<Note>
`TWENTY_FUNCTIONS_URL` и `TWENTY_APP_ACCESS_TOKEN` внедряются автоматически — см. [переменные приложения](#application-variables). Поскольку секретные переменные приложения никогда не раскрываются front-компонентам, храните ключи API и другую конфиденциальную логику в логической функции, а не во front-компоненте.
</Note>

### Вызов REST API Twenty

Чтобы читать или изменять записи Twenty из фронт-компонента, используйте `RestApiClient` из `twenty-client-sdk/rest`. Он принадлежит к тому же семейству клиентов, что и `CoreApiClient` и `MetadataApiClient`, но нацелен на REST API Twenty (`/rest/...`) вместо GraphQL API, считывая базовый URL из `TWENTY_API_URL`.

| Метод                             | Описание                                  |
| --------------------------------- | ----------------------------------------- |
| `get(path, options?)`             | Отправляет запрос `GET`                   |
| `post(path, body?, options?)`     | Отправляет запрос `POST`                  |
| `put(path, body?, options?)`      | Отправляет запрос `PUT`                   |
| `patch(path, body?, options?)`    | Отправляет запрос `PATCH`                 |
| `delete(path, options?)`          | Отправляет запрос `DELETE`                |
| `request(method, path, options?)` | Универсальный запрос с любым HTTP-методом |

В `options` принимаются `headers`, `query` (объект с параметрами строки запроса; значения, равные null или undefined, пропускаются) и `AbortSignal` через `signal`. Объект `body`, не являющийся `FormData`, автоматически сериализуется в JSON. При получении `401` клиент один раз обновляет токен доступа через хост и повторяет запрос.

Базовый URL и токен по умолчанию берутся из окружения. При необходимости передавайте переопределения в конструктор — например, в тестах:

```ts
const client = new RestApiClient({
  baseUrl: 'https://myworkspace.twenty.com',
  token: 'my-token',
});
```

Неудачные запросы выбрасывают `RestApiClientError`, который содержит `status`, `statusText`, `url` и распарсенное `body`:

```tsx
import { RestApiClient, RestApiClientError } from 'twenty-client-sdk/rest';

const client = new RestApiClient();

try {
  const people = await client.get('/rest/people', {
    query: { limit: 10 },
  });
} catch (error) {
  if (error instanceof RestApiClientError) {
    console.error(error.status, error.body);
  }
}
```

## Доступ к контексту времени выполнения

Внутри вашего компонента используйте хуки SDK для доступа к текущему пользователю, записи и экземпляру компонента:

```tsx src/front-components/record-info.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import {
  useUserId,
  useRecordId,
  useFrontComponentId,
} from 'twenty-sdk/front-component';

const RecordInfo = () => {
  const userId = useUserId();
  const recordId = useRecordId();
  const componentId = useFrontComponentId();

  return (
    <div>
      <p>User: {userId}</p>
      <p>Record: {recordId ?? 'No record context'}</p>
      <p>Component: {componentId}</p>
    </div>
  );
};

export default defineFrontComponent({
  universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012',
  name: 'record-info',
  component: RecordInfo,
});
```

Доступные хуки:

| Хук                                           | Возвращает             | Описание                                                                     |
| --------------------------------------------- | ---------------------- | ---------------------------------------------------------------------------- |
| `useUserId()`                                 | `string` или `null`    | ID текущего пользователя                                                     |
| `useSelectedRecordIds()`                      | `string[]`             | Все выбранные идентификаторы записей (пустой массив, если ничего не выбрано) |
| `useRecordId()`                               | `string` или `null`    | **Устарело.** Используйте `useSelectedRecordIds()` вместо этого              |
| `useFrontComponentId()`                       | `string`               | ID этого экземпляра компонента                                               |
| `useColorScheme()`                            | `'light'` или `'dark'` | Активная цветовая схема интерфейса хоста (значение `System` уже определено)  |
| `useFrontComponentExecutionContext(selector)` | различается            | Доступ к полному контексту выполнения с помощью функции-селектора            |

## Переменные приложения

Переменные приложения, определенные в [`defineApplication()`](/l/ru/developers/extend/apps/config/application) с `isSecret: false`, доступны внутри фронтенд-компонентов через утилиту `getApplicationVariable`:

```tsx src/front-components/greeting.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getApplicationVariable } from 'twenty-sdk/front-component';

const Greeting = () => {
  const recipientName = getApplicationVariable('DEFAULT_RECIPIENT_NAME') ?? 'World';

  return <p>Hello, {recipientName}!</p>;
};

export default defineFrontComponent({
  universalIdentifier: '...',
  name: 'greeting',
  component: Greeting,
});
```

<Warning>
Секретные переменные (`isSecret: true`) **не** доступны фронтенд-компонентам. Они доступны только в [логических функциях](/l/ru/developers/extend/apps/logic/logic-functions), которые выполняются на стороне сервера. Это предотвращает отправку в браузер конфиденциальных значений, таких как ключи API.
</Warning>

Следующие системные переменные всегда доступны через `process.env`:

| Переменная                | Описание                                                                      |
| ------------------------- | ----------------------------------------------------------------------------- |
| `TWENTY_FUNCTIONS_URL`    | Базовый URL, с которого обслуживаются функции HTTP-логики вашего приложения   |
| `TWENTY_API_URL`          | Базовый URL основного API Twenty                                              |
| `TWENTY_APP_ACCESS_TOKEN` | Краткоживущий токен с областью действия, ограниченной ролью вашего приложения |

## API взаимодействия с хостом

Компоненты фронтенда могут вызывать навигацию, модальные окна и уведомления с помощью функций из `twenty-sdk`:

| Функция                                         | Описание                         |
| ----------------------------------------------- | -------------------------------- |
| `navigate(to, params?, queryParams?, options?)` | Перейти на страницу в приложении |
| `openSidePanelPage(params)`                     | Открыть боковую панель           |
| `closeSidePanel()`                              | Закрыть боковую панель           |
| `openCommandConfirmationModal(params)`          | Показать диалог подтверждения    |
| `enqueueSnackbar(params)`                       | Показать всплывающее уведомление |
| `unmountFrontComponent()`                       | Размонтировать компонент         |
| `updateProgress(progress)`                      | Обновить индикатор прогресса     |

Пример, который использует API хоста для показа snackbar и закрытия боковой панели после завершения действия:

```tsx src/front-components/archive-record.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';

const ArchiveRecord = () => {
  const recordId = useRecordId();

  const handleArchive = async () => {
    const client = new CoreApiClient();

    await client.mutation({
      updateTask: {
        __args: { id: recordId, data: { status: 'ARCHIVED' } },
        id: true,
      },
    });

    await enqueueSnackbar({
      message: 'Record archived',
      variant: 'success',
    });

    await closeSidePanel();
  };

  return (
    <div style={{ padding: '20px' }}>
      <p>Archive this record?</p>
      <button onClick={handleArchive}>Archive</button>
    </div>
  );
};

export default defineFrontComponent({
  universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678',
  name: 'archive-record',
  description: 'Archives the current record',
  component: ArchiveRecord,
});
```

### Работа с несколькими записями

Используйте `useSelectedRecordIds()` для обработки нескольких выбранных записей. Это полезно для массовых операций:

```tsx src/front-components/bulk-export.tsx
import { defineFrontComponent, numberOfSelectedRecords } from 'twenty-sdk/define';
import { useSelectedRecordIds } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';

const BulkExport = () => {
  const selectedRecordIds = useSelectedRecordIds();

  const handleExport = async () => {
    const client = new CoreApiClient();

    for (const recordId of selectedRecordIds) {
      await client.mutation({
        updateTask: {
          __args: { id: recordId, data: { exported: true } },
          id: true,
        },
      });
    }

    await enqueueSnackbar({
      message: `Exported ${selectedRecordIds.length} records`,
      variant: 'success',
    });

    await closeSidePanel();
  };

  return (
    <div style={{ padding: '20px' }}>
      <p>Export {selectedRecordIds.length} selected record(s)?</p>
      <button onClick={handleExport}>Export</button>
    </div>
  );
};

export default defineFrontComponent({
  universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678901',
  name: 'bulk-export',
  description: 'Export selected records',
  component: BulkExport,
  command: {
    universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678902',
    label: 'Bulk Export',
    availabilityType: 'RECORD_SELECTION',
    conditionalAvailabilityExpression: numberOfSelectedRecords > 0,
  },
});
```

## Публичные ресурсы

Компоненты фронтенда могут получать доступ к файлам из каталога приложения `public/` с помощью `getPublicAssetUrl`:

```tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getPublicAssetUrl } from 'twenty-sdk/utils';

const Logo = () => <img src={getPublicAssetUrl('logo.png')} alt="Logo" />;

export default defineFrontComponent({
  universalIdentifier: '...',
  name: 'logo',
  component: Logo,
});
```

См. [раздел о публичных ресурсах](/l/ru/developers/extend/apps/config/public-assets) для подробностей.

## Стилизация

Компоненты фронтенда поддерживают несколько подходов к стилизации. Вы можете использовать:

* **Встроенные стили** — `style={{ color: 'red' }}`
* **Twenty UI components** — собственная библиотека компонентов Twenty; см. раздел [Using Twenty UI components](#using-twenty-ui-components) ниже
* **Emotion** — CSS-in-JS с `@emotion/react`
* **Styled-components** — паттерны `styled.div`
* **Tailwind CSS** — утилитарные классы
* **Любая библиотека CSS-in-JS**, совместимая с React

## Using Twenty UI components

Twenty поставляет свою библиотеку компонентов как пакет [`twenty-ui`](https://www.npmjs.com/package/twenty-ui/v/1.0.0-alpha.1). Front components can use it for buttons, tags, status pills, chips, avatars, icons, typography, and theme tokens that automatically match the workspace's light and dark theme.

### Установка

Add the package to your app, pinned to the version your Twenty instance ships:

```bash
yarn add twenty-ui@1.0.0-alpha.1
```

`twenty-ui` is bundled into your front component at build time, so it only needs to be a dependency of your app — there is nothing to configure at runtime.

### Importing components

Import from the matching subpath rather than the package root, so only the components you use end up in your bundle:

| Subpath                     | What it exports                                    |
| --------------------------- | -------------------------------------------------- |
| `twenty-ui/input`           | `Button` and form inputs                           |
| `twenty-ui/data-display`    | `Tag`, `Status`, `Chip`, `Avatar`, and more        |
| `twenty-ui/feedback`        | `Callout`, `Banner`, `Info`, and more              |
| `twenty-ui/typography`      | `H1Title`, `H2Title`, `H3Title`, `Label`, and more |
| `twenty-ui/icon`            | `Icon*` components (e.g. `IconCheck`)              |
| `twenty-ui/theme-constants` | `ThemeProvider`, `themeCssVariables`               |

```tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Status, Tag } from 'twenty-ui/data-display';
import { Button } from 'twenty-ui/input';

const StyledWidget = () => {
  return (
    <div style={{ padding: '16px', display: 'flex', gap: '8px' }}>
      <Button title="Click me" onClick={() => alert('Clicked!')} />
      <Tag text="Active" color="green" />
      <Status color="green" text="Online" />
    </div>
  );
};

export default defineFrontComponent({
  universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456',
  name: 'styled-widget',
  component: StyledWidget,
});
```

### Иконки

Import individual icons from `twenty-ui/icon`:

```tsx
import { IconBox, IconCheck } from 'twenty-ui/icon';
```

Each named icon is tree-shaken, so importing a handful adds little to your bundle. Avoid `IconsProvider`, `useIcons`, and `iconsState` — they pull in the full Tabler icon set (several MB).

### Theming and theme tokens

Twenty UI components automatically match the workspace's light and dark theme — the renderer applies the active color scheme on the host, and the components resolve their colors against it.

To use the same design tokens in your own inline styles, call the `useTheme()` hook. It returns Twenty's theme tokens (spacing, colors, radii, fonts) wired to the active theme, with no `ThemeProvider` setup needed in your component:

```tsx
import { useTheme } from 'twenty-ui/theme-constants';

const Card = () => {
  const theme = useTheme();

  return (
    <div
      style={{
        padding: theme.spacing[4],
        background: theme.background.secondary,
        color: theme.font.color.primary,
      }}
    >
      Themed card
    </div>
  );
};
```

Because `useTheme()` is a hook, you read tokens inside the component body, so the values always reflect the live theme. The same token map is also exported as the `themeCssVariables` constant, but prefer `useTheme()` in front components — a module-level constant that dereferences `themeCssVariables` can be undefined while the app manifest is extracted.

To branch on the active scheme explicitly, read it with `useColorScheme()` from `twenty-sdk/front-component`, which returns `'light'` or `'dark'`.
