Files
twenty/packages/twenty-docs/l/de/developers/extend/apps/layout/front-components.mdx
T
github-actions[bot] b7fc0872c8 i18n - docs translations (#22511)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22511?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-03 11:42:02 +02:00

622 lines
28 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Frontend-Komponenten
description: Erstellen Sie React-Komponenten, die innerhalb der Twenty-UI gerendert werden und durch eine Sandbox isoliert sind.
icon: window-maximize
---
Front-Komponenten sind React-Komponenten, die direkt innerhalb der Twenty-UI gerendert werden. Sie laufen in einem **isolierten Web Worker** unter Verwendung von Remote DOM — Ihr Code wird in einer Sandbox ausgeführt, rendert jedoch nativ auf der Seite, nicht in einem iframe.
## Wo Front-Komponenten verwendet werden können
Front-Komponenten können an zwei Stellen innerhalb von Twenty gerendert werden:
* **Seitenpanel** — Nicht-Headless-Front-Komponenten werden im rechten Seitenpanel geöffnet. Dies ist das Standardverhalten, wenn eine Front-Komponente über das Befehlsmenü ausgelöst wird.
* **Widgets (Dashboards und Datensatzseiten)** — Front-Komponenten können als Widgets in [Seitenlayouts](/l/de/developers/extend/apps/layout/page-layouts) eingebettet werden. Beim Konfigurieren eines Dashboards oder eines Datensatzseiten-Layouts können Benutzer ein Front-Komponenten-Widget hinzufügen.
Eine Front-Komponente allein ist über die Benutzeroberfläche nicht erreichbar Sie müssen sie *sichtbar machen*. Die beiden Möglichkeiten dafür sind:
* **Mit einem [Befehlsmenüeintrag](/l/de/developers/extend/apps/layout/command-menu-items) verknüpfen** — registriert sie im Befehlsmenü (Cmd+K) und optional als angeheftete Schnellaktion.
* **Als Widget in ein [Seitenlayout](/l/de/developers/extend/apps/layout/page-layouts) einbetten** — platziert es auf der Detailseite eines Datensatzes oder in einem Dashboard.
## Einfaches Beispiel
Die schnellste Möglichkeit, eine Front-Komponente in Aktion zu sehen, besteht darin, sie mit einem [`defineCommandMenuItem`](/l/de/developers/extend/apps/layout/command-menu-items) zu verknüpfen, sodass sie als Schnellaktionsschaltfläche in der oberen rechten Ecke der Seite erscheint:
```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',
});
```
Nach dem Synchronisieren mit `yarn twenty dev` (oder durch einmaliges Ausführen von `yarn twenty dev --once`) erscheint die Schnellaktion oben rechts auf der Seite:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="Schnellaktionsschaltfläche oben rechts" />
</div>
Klicken Sie darauf, um die Komponente inline zu rendern.
## Konfigurationsfelder
| Feld | Erforderlich | Beschreibung |
| --------------------- | ------------ | --------------------------------------------------------------------------- |
| `universalIdentifier` | Ja | Stabile eindeutige ID für diese Komponente |
| `component` | Ja | Eine React-Komponentenfunktion |
| `name` | Nein | Anzeigename |
| `description` | Nein | Beschreibung dessen, was die Komponente macht |
| `isHeadless` | Nein | Auf `true` setzen, wenn die Komponente keine sichtbare UI hat (siehe unten) |
## Eine Front-Komponente auf einer Seite platzieren
Über Befehle hinaus können Sie eine Front-Komponente direkt in eine Datensatzseite einbetten, indem Sie sie als Widget in einem **Seitenlayout** hinzufügen. Details finden Sie unter [Seitenlayouts](/l/de/developers/extend/apps/layout/page-layouts).
## Headless vs. Nicht-Headless
Front-Komponenten gibt es in zwei Rendering-Modi, die durch die Option `isHeadless` gesteuert werden:
**Nicht-Headless (Standard)** — Die Komponente rendert eine sichtbare UI. Wird sie über das Befehlsmenü ausgelöst, öffnet sie sich im Seitenpanel. Dies ist das Standardverhalten, wenn `isHeadless` `false` ist oder weggelassen wird.
**Headless (`isHeadless: true`)** — Die Komponente wird unsichtbar im Hintergrund gemountet. Sie öffnet das Seitenpanel nicht. Headless-Komponenten sind für Aktionen konzipiert, die Logik ausführen und sich anschließend selbst unmounten — zum Beispiel das Ausführen einer asynchronen Aufgabe, das Navigieren zu einer Seite oder das Anzeigen eines Bestätigungsdialogs. Sie lassen sich gut mit den unten beschriebenen SDK-Command-Komponenten kombinieren.
```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,
});
```
Da die Komponente `null` zurückgibt, überspringt Twenty das Rendern eines Containers dafür — im Layout entsteht kein Leerraum. Die Komponente hat dennoch Zugriff auf alle Hooks und die Host-Kommunikations-API.
## SDK-Command-Komponenten
Das Paket `twenty-sdk` stellt vier Command-Hilfskomponenten bereit, die für Headless-Front-Komponenten ausgelegt sind. Jede Komponente führt beim Mounten eine Aktion aus, behandelt Fehler durch Anzeige einer Snackbar-Benachrichtigung und unmountet die Front-Komponente nach Abschluss automatisch.
Importieren Sie sie aus `twenty-sdk/command`:
* **`Command`** — Führt einen asynchronen Callback über das Prop `execute` aus.
* **`CommandLink`** — Navigiert zu einem App-Pfad. Props: `to`, `params`, `queryParams`, `options`.
* **`CommandModal`** — Öffnet einen Bestätigungsdialog. Bestätigt der Benutzer, wird der Callback `execute` ausgeführt. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`.
* **`CommandOpenSidePanelPage`** — Öffnet eine Seite im Seitenpanel. Props hängen von `page` ab — z. B. verwendet `ViewRecord` `recordId` + `objectNameSingular`, andere Seiten verwenden `pageTitle` + `pageIcon`.
Hier ist ein vollständiges Beispiel einer Headless-Front-Komponente, die `Command` verwendet, um eine Aktion aus dem Befehlsmenü auszuführen:
```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',
});
```
Und ein Beispiel, das `CommandModal` verwendet, um vor der Ausführung um Bestätigung zu bitten:
```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,
});
```
## Aufrufen einer Logikfunktion
Front-Komponenten laufen browserseitig in einem isolierten Web Worker, während [Logikfunktionen](/l/de/developers/extend/apps/logic/logic-functions) serverseitig ausgeführt werden. Es gibt keinen direkten In-Process-Aufruf zwischen beiden stattdessen ruft eine Front-Komponente eine Logikfunktion über HTTP auf.
Eine mit `httpRouteTriggerSettings` deklarierte Logikfunktion ist über HTTP unter ihrem Routenpfad erreichbar. Twenty injiziert die Basis-URL, unter der deine Funktionen bereitgestellt werden, als `TWENTY_FUNCTIONS_URL` in den Worker, zusammen mit dem `TWENTY_APP_ACCESS_TOKEN`, das den Aufruf authentifiziert. Es gibt noch keinen eigenen SDK-Client zum Aufrufen deiner eigenen Funktionen, daher rufe sie mit einem einfachen `fetch` auf:
> **In Twenty Cloud werden HTTP-ausgelöste Logikfunktionen auf einer eigenen, arbeitsbereichsspezifischen Domain bereitgestellt** unter `https://\<your-workspace-subdomain>.twenty.com\<path>` — genau darauf verweist `TWENTY_FUNCTIONS_URL`. Für externe Aufrufer kopiere die exakte URL aus den **HTTP trigger**-Einstellungen der Funktion oder aus dem **Settings**-Tab der Anwendung.
<Warning>
Die `/s/`-Funktionsroute ist **veraltet** und wird **am 2026-07-24 deaktiviert**. Verwende stattdessen `TWENTY_FUNCTIONS_URL` (oben) und migriere alle hart codierten `/s/`-URLs vor diesem Datum. Die `/s/`-Route bleibt für Self-Hosting verfügbar.
</Warning>
Eine headless Front-Komponente kann den Aufruf beim Mounten über die `Command`-Komponente ausführen und sich anschließend automatisch unmounten:
```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,
});
```
Der an `TWENTY_FUNCTIONS_URL` angehängte Pfad ist das `httpRouteTriggerSettings.path` der Logikfunktion. Belasse `isAuthRequired: true`; das `TWENTY_APP_ACCESS_TOKEN`, das Twenty für deine Komponente ausstellt, authentifiziert die Anfrage:
```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` und `TWENTY_APP_ACCESS_TOKEN` werden automatisch injiziert siehe [Anwendungsvariablen](#application-variables). Da geheime Anwendungsvariablen niemals in Front-Komponenten offengelegt werden, sollten API-Schlüssel und andere sensible Logik in der Logikfunktion verbleiben und nicht in der Front-Komponente.
</Note>
### Aufrufen der Twenty-REST-API
Um Twenty-Datensätze aus einer Front-Komponente zu lesen oder zu schreiben, verwende `RestApiClient` aus `twenty-client-sdk/rest`. Er gehört zur gleichen Client-Familie wie `CoreApiClient` und `MetadataApiClient`, zielt jedoch auf die Twenty-REST-API (`/rest/...`) statt auf die GraphQL-API und liest deren Basis-URL aus `TWENTY_API_URL`.
| Methode | Beschreibung |
| --------------------------------- | ---------------------------------------------------- |
| `get(path, options?)` | Sendet eine `GET`-Anfrage |
| `post(path, body?, options?)` | Sendet eine `POST`-Anfrage |
| `put(path, body?, options?)` | Sendet eine `PUT`-Anfrage |
| `patch(path, body?, options?)` | Sendet eine `PATCH`-Anfrage |
| `delete(path, options?)` | Sendet eine `DELETE`-Anfrage |
| `request(method, path, options?)` | Generische Anfrage mit einer beliebigen HTTP-Methode |
`options` akzeptiert `headers`, `query` (ein Record von Query-String-Parametern; null- bzw. undefined-Werte werden übersprungen) sowie ein `AbortSignal` über `signal`. Ein `body`-Objekt, das kein `FormData` ist, wird automatisch als JSON serialisiert. Bei einem `401` aktualisiert der Client das Access-Token einmal über den Host und versucht die Anfrage erneut.
Die Basis-URL und das Token werden standardmäßig aus der Umgebung ermittelt. Gib bei Bedarf zum Beispiel in Tests Überschreibungen an den Konstruktor weiter:
```ts
const client = new RestApiClient({
baseUrl: 'https://myworkspace.twenty.com',
token: 'my-token',
});
```
Fehlgeschlagene Anfragen lösen einen `RestApiClientError` aus, der `status`, `statusText`, `url` und den geparsten `body` bereitstellt:
```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);
}
}
```
## Zugriff auf den Laufzeitkontext
Verwenden Sie innerhalb Ihrer Komponente SDK-Hooks, um auf den aktuellen Benutzer, den Datensatz und die Komponenteninstanz zuzugreifen:
```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,
});
```
Verfügbare Hooks:
| Hook | Gibt zurück | Beschreibung |
| --------------------------------------------- | ----------------------- | --------------------------------------------------------------------------- |
| `useUserId()` | `string` oder `null` | Die ID des aktuellen Benutzers |
| `useSelectedRecordIds()` | `string[]` | Alle ausgewählten Datensatz-IDs (leeres Array, wenn keine ausgewählt sind) |
| `useRecordId()` | `string` oder `null` | **Veraltet.** Verwenden Sie stattdessen `useSelectedRecordIds()` |
| `useFrontComponentId()` | `string` | Die ID dieser Komponenteninstanz |
| `useColorScheme()` | `'light'` oder `'dark'` | Das aktive Farbschema der Host-UI (`System` ist bereits aufgelöst) |
| `useFrontComponentExecutionContext(selector)` | variiert | Zugriff auf den vollständigen Ausführungskontext mit einer Selektorfunktion |
## Anwendungsvariablen
In [`defineApplication()`](/l/de/developers/extend/apps/config/application) mit `isSecret: false` definierte Anwendungsvariablen sind in Front-Komponenten über das Hilfsprogramm `getApplicationVariable` verfügbar:
```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>
Geheime Variablen (`isSecret: true`) werden **nicht** in Front-Komponenten offengelegt. Sie sind nur in [Logikfunktionen](/l/de/developers/extend/apps/logic/logic-functions) verfügbar, die serverseitig ausgeführt werden. Dadurch wird verhindert, dass sensible Werte wie API-Schlüssel an den Browser gesendet werden.
</Warning>
`getApplicationVariable` gibt immer einen **String** (oder `undefined`) zurück, unabhängig vom deklarierten `type` der Variable. Der String wird je nach Typ konsistent serialisiert (boolesche Werte als `"true"` / `"false"`, Zahlen als Dezimalstrings, Arrays / Objekte als JSON), im selben Format, das für die Logikfunktion `process.env` verwendet wird — parsen Sie ihn selbst (`Number(...)`, `JSON.parse(...)`, `=== 'true'`). Siehe [Variablentypen](/l/de/developers/extend/apps/config/application#variable-types).
Die folgenden Systemvariablen sind immer über `process.env` verfügbar:
| Variable | Beschreibung |
| ------------------------- | ---------------------------------------------------------------------------- |
| `TWENTY_FUNCTIONS_URL` | Basis-URL, von der die HTTP-Logikfunktionen deiner App bereitgestellt werden |
| `TWENTY_API_URL` | Basis-URL der Twenty-Core-API |
| `TWENTY_APP_ACCESS_TOKEN` | Kurzlebiges Token mit dem Geltungsbereich der Rolle Ihrer App |
## Host-Kommunikations-API
Front-Komponenten können Navigation, Modals und Benachrichtigungen mittels Funktionen aus `twenty-sdk` auslösen:
| Funktion | Beschreibung |
| ----------------------------------------------- | ----------------------------------------- |
| `navigate(to, params?, queryParams?, options?)` | Zu einer Seite in der App navigieren |
| `openSidePanelPage(params)` | Ein Seitenpanel öffnen |
| `closeSidePanel()` | Seitenpanel schließen |
| `openCommandConfirmationModal(params)` | Einen Bestätigungsdialog anzeigen |
| `enqueueSnackbar(params)` | Eine Toast-Benachrichtigung anzeigen |
| `unmountFrontComponent()` | Die Komponente entfernen |
| `updateProgress(progress)` | Einen Fortschrittsindikator aktualisieren |
Hier ist ein Beispiel, das die Host-API verwendet, um nach Abschluss einer Aktion eine Snackbar anzuzeigen und das Seitenpanel zu schließen:
```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,
});
```
### Mit mehreren Datensätzen arbeiten
Verwenden Sie `useSelectedRecordIds()`, um mehrere ausgewählte Datensätze zu verwalten. Dies ist nützlich für Stapelvorgänge:
```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,
},
});
```
## Öffentliche Assets
Front-Komponenten können mit `getPublicAssetUrl` auf Dateien aus dem `public/`-Verzeichnis der App zugreifen:
```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,
});
```
Details finden Sie im Abschnitt [Öffentliche Assets](/l/de/developers/extend/apps/config/public-assets).
## Styling
Front-Komponenten unterstützen mehrere Styling-Ansätze. Sie können verwenden:
* **Inline-Styles** — `style={{ color: 'red' }}`
* **Twenty UI-Komponenten** — Twentys eigene Komponentenbibliothek; siehe [Verwenden von Twenty UI-Komponenten](#using-twenty-ui-components) unten
* **Emotion** — CSS-in-JS mit `@emotion/react`
* **Styled-components** — `styled.div`-Muster
* **Tailwind CSS** — Utility-Klassen
* **Beliebige CSS-in-JS-Bibliothek**, die mit React kompatibel ist
## Verwenden von Twenty UI-Komponenten
Twenty liefert seine Komponentenbibliothek als [`twenty-ui`](https://www.npmjs.com/package/twenty-ui/v/1.0.0-alpha.1)-Paket aus. Frontend-Komponenten können sie für Schaltflächen, Tags, Status-Pills, Chips, Avatare, Icons, Typografie und Theme-Tokens verwenden, die automatisch zum hellen und dunklen Theme des Arbeitsbereichs passen.
### Installation
Fügen Sie das Paket zu Ihrer App hinzu und fixieren Sie es auf die Version, mit der Ihre Twenty-Instanz ausgeliefert wird:
```bash
yarn add twenty-ui@1.0.0-alpha.1
```
`twenty-ui` wird zur Build-Zeit in Ihre Frontend-Komponente gebündelt, sodass es nur eine Abhängigkeit Ihrer App sein muss zur Laufzeit muss nichts konfiguriert werden.
### Komponenten importieren
Importieren Sie aus dem passenden Subpfad statt aus dem Paket-Root, damit nur die Komponenten, die Sie verwenden, in Ihrem Bundle landen:
| Subpfad | Was exportiert wird |
| --------------------------- | ------------------------------------------------- |
| `twenty-ui/input` | `Button`- und Formulareingaben |
| `twenty-ui/data-display` | `Tag`, `Status`, `Chip`, `Avatar` und mehr |
| `twenty-ui/feedback` | `Callout`, `Banner`, `Info` und mehr |
| `twenty-ui/typography` | `H1Title`, `H2Title`, `H3Title`, `Label` und mehr |
| `twenty-ui/icon` | `Icon*`-Komponenten (z.B. `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,
});
```
### Icons
Importieren Sie einzelne Icons aus `twenty-ui/icon`:
```tsx
import { IconBox, IconCheck } from 'twenty-ui/icon';
```
Jedes benannte Icon wird per Tree Shaking berücksichtigt, sodass der Import einiger weniger Ihr Bundle nur gering vergrößert. Vermeiden Sie `IconsProvider`, `useIcons` und `iconsState` sie ziehen das vollständige Tabler-Icon-Set (mehrere MB) mit hinein.
### Theming und Theme-Tokens
Twenty UI-Komponenten passen sich automatisch dem hellen und dunklen Theme des Arbeitsbereichs an der Renderer wendet das aktive Farbschema auf dem Host an und die Komponenten leiten ihre Farben daraus ab.
Um dieselben Design-Tokens in Ihren eigenen Inline-Styles zu verwenden, rufen Sie den Hook `useTheme()` auf. Er gibt die Theme-Tokens von Twenty (Abstände, Farben, Radien, Schriftarten) zurück, die mit dem aktiven Theme verknüpft sind ohne dass ein `ThemeProvider` in Ihrer Komponente eingerichtet werden muss:
```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>
);
};
```
Da `useTheme()` ein Hook ist, lesen Sie Tokens im Komponenten-Body aus, sodass die Werte immer das aktuelle Theme widerspiegeln. Dieselbe Token-Map wird auch als Konstante `themeCssVariables` exportiert, aber bevorzugen Sie `useTheme()` in Frontend-Komponenten eine modulweite Konstante, die `themeCssVariables` dereferenziert, kann undefiniert sein, während das App-Manifest extrahiert wird.
Um explizit nach dem aktiven Schema zu verzweigen, lesen Sie es mit `useColorScheme()` aus `twenty-sdk/front-component` aus; der Hook gibt 'light' oder 'dark' zurück.