> ## Documentation Index
> Fetch the complete documentation index at: https://www.ayrshare.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Widget Ayrshare Connect

> Permettez à vos utilisateurs de lier leurs comptes sociaux depuis votre propre tableau de bord, avec une seule balise script et nos boutons intégrés dans votre page.

export const PlansAvailable = ({plans = [], maxPackRequired}) => {
  let displayPlans = plans;
  if (plans && plans.length === 1) {
    const lowerCasePlan = plans[0].toLowerCase();
    if (lowerCasePlan === "business") {
      displayPlans = ["Launch", "Business", "Enterprise"];
    } else if (lowerCasePlan === "premium") {
      displayPlans = ["Premium", "Launch", "Business", "Enterprise"];
    }
  }
  return <Note>
Available on {displayPlans.length === 1 ? "the " : ""}
{displayPlans.join(", ").replace(/\b\w/g, l => l.toUpperCase())}{" "}
{displayPlans.length > 1 ? "plans" : "plan"}.

{maxPackRequired && <span onClick={() => window.open('https://www.ayrshare.com/docs/additional/maxpack', '_self')} className="flex items-center mt-2 cursor-pointer">
 <span className="px-1.5 py-0.5 rounded text-sm" style={{
    backgroundColor: '#C264B6',
    color: 'white',
    fontSize: '12px'
  }}>
   Max Pack required
 </span>
</span>}
</Note>;
};

<PlansAvailable plans={["business"]} maxPackRequired={true} />

Le widget Ayrshare Connect place nos boutons de liaison **à l'intérieur de votre propre tableau de
bord**. Vous chargez un script, déposez un emplacement (slot) là où un réseau a sa place dans votre
mise en page, et nous y affichons un bouton qui indique déjà si le compte est connecté. Votre
utilisateur clique dessus et lie le compte sans quitter votre page — au plus une popup, celle du
réseau lui-même.

Vous n'écrivez aucune gestion de popup, aucun callback OAuth, aucun rafraîchissement de session ni
aucune logique par réseau. Lorsqu'un réseau change quelque chose de son côté, le correctif est livré
dans nos frames dès que nous le déployons ; vous ne redéployez rien.

## Quelle surface choisir

| Surface                                                                                                                         | Votre utilisateur voit                                                                               | Marque blanche                                                                                       | Choisissez-la quand                                                                                           |
| ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| **Widget — frames intégrées** ([`mount()`](#mount-a-slot))                                                                      | nos boutons directement dans votre propre mise en page ; aucune popup avant celle du réseau lui-même | **La plus forte.** Votre page, vos polices et vos couleurs, et votre utilisateur ne la quitte jamais | vous avez un tableau de bord avec une ligne par réseau et voulez que la liaison se fasse sur place. Max Pack. |
| **Widget — votre propre bouton** ([`popup()`](#your-own-button))                                                                | votre bouton, puis une seule popup pour le réseau                                                    | **Forte.** La popup est la nôtre, mais elle est brève et hérite de votre apparence                   | vous voulez votre propre bouton et votre propre style, et aucune frame dans votre mise en page. Max Pack.     |
| **Page de liaison hébergée** ([mode d'emploi](/docs/multiple-users/api-integration-business#single-sign-on-with-jwt-authentication)) | une page que nous hébergeons, portant votre logo, vos couleurs et votre CSS personnalisé             | **La plus faible.** C'est notre page, et votre utilisateur quitte la vôtre pour l'utiliser           | vous voulez un lien unique à distribuer, ou vous intégrez par e-mail. Rien à construire, pas de Max Pack.     |

<div className="my-8">
  <Frame caption="Frames intégrées : nos tuiles affichées dans votre propre mise en page, un emplacement par réseau ou un emplacement pour plusieurs.">
    <img src="https://mintcdn.com/ayrshare-docs/-2tpx3mFy9vWIObd/images/multiple-users/connect-widget-frames.webp?fit=max&auto=format&n=-2tpx3mFy9vWIObd&q=85&s=d6c26228a418421772f753f31b0dfc59" alt="A customer dashboard with Ayrshare Connect tiles for Instagram, TikTok and LinkedIn embedded in a card" width="2400" height="1120" data-path="images/multiple-users/connect-widget-frames.webp" />
  </Frame>
</div>

<div className="my-8">
  <Frame caption="Votre propre bouton : vous affichez le bouton, et une brève popup de notre côté gère le réseau.">
    <img src="https://mintcdn.com/ayrshare-docs/-2tpx3mFy9vWIObd/images/multiple-users/connect-widget-popup.webp?fit=max&auto=format&n=-2tpx3mFy9vWIObd&q=85&s=b68021d477703d97a5994ee85d299be6" alt="A customer dashboard with its own Connect buttons and an Ayrshare popup showing the Facebook hand-off screen" width="2880" height="1317" data-path="images/multiple-users/connect-widget-popup.webp" />
  </Frame>
</div>

<div className="my-8">
  <Frame caption="Page de liaison hébergée : une page que nous hébergeons, avec votre logo et vos couleurs, que votre utilisateur quitte votre application pour utiliser.">
    <img src="https://mintcdn.com/ayrshare-docs/-2tpx3mFy9vWIObd/images/multiple-users/connect-widget-hosted.webp?fit=max&auto=format&n=-2tpx3mFy9vWIObd&q=85&s=4d22a992f6e70808fd9ad9f065ed3879" alt="The hosted social linking page showing every available network" width="2336" height="1906" data-path="images/multiple-users/connect-widget-hosted.webp" />
  </Frame>
</div>

<Note>
  Les deux lignes du widget sont **une seule intégration**, pas deux. Un seul `init` vous donne les
  deux : montez des frames là où vous voulez nos boutons, et appelez `popup()` depuis votre propre
  bouton partout ailleurs. Elles partagent une même session et rapportent sur les mêmes handlers.

  Le [mode direct](/docs/multiple-users/connect-direct-mode) est la même popup **sans** notre script —
  pour une page avec une Content-Security-Policy stricte, une page rendue côté serveur ou une
  application native. Là, c'est vous qui ouvrez et surveillez la popup.
</Note>

## Ajouter le script

Épinglez une version et son hash, ou suivez un canal sans hash. Jamais les deux — un attribut
`integrity` sur une URL mouvante cesse de fonctionner à notre prochaine release, car le fichier
qu'elle désigne a légitimement changé.

```html Pinned version theme={"system"}
<script
  src="https://app.ayrshare.com/ayrshare-connect/1.2.0/widget.js"
  integrity="sha384-qQ/of8CnYj26nF9RkQ6qk3D1vvzWesu7mpBK6MFK2D0EmmgRJHfbDmRowFBfltmz"
  crossorigin="anonymous"
></script>
```

```html Tracking v1 theme={"system"}
<script src="https://app.ayrshare.com/ayrshare-connect/v1/widget.js" crossorigin="anonymous"></script>
```

| Chemin                                  | Mise en cache   | `integrity`           |
| --------------------------------------- | --------------- | --------------------- |
| `/ayrshare-connect/<version>/widget.js` | immuable, un an | **oui** — épinglez-le |
| `/ayrshare-connect/v1/widget.js`        | cinq minutes    | non                   |
| `/ayrshare-connect/latest/widget.js`    | cinq minutes    | non                   |

**`v1` est le canal à recommander.** Il récupère les correctifs mais ne franchit jamais un
changement cassant. `latest` franchit les versions majeures par définition, donc il finira par
livrer à votre page une version dont vous n'avez pas examiné le comportement.

Le hash de chaque version est publié dans
[`manifest.json`](https://app.ayrshare.com/ayrshare-connect/manifest.json), qui indique également
ce que chaque canal sert actuellement :

```json manifest.json theme={"system"}
{
  "versions": {
    "1.2.0": { "integrity": "sha384-qQ/of8CnYj26nF9RkQ6qk3D1vvzWesu7mpBK6MFK2D0EmmgRJHfbDmRowFBfltmz" }
  },
  "latest": "1.2.0",
  "channels": { "v1": "1.2.0", "latest": "1.2.0" }
}
```

Chaque bundle s'ouvre également sur un commentaire indiquant sa propre version, ce qui est le moyen
le plus rapide de nous dire ce qu'une page exécute réellement :

```js theme={"system"}
/*! ayrshare-connect 1.2.0 */
```

### Content-Security-Policy

Si votre page envoie une Content-Security-Policy, il lui faut **deux** entrées, toutes deux
désignant l'hôte depuis lequel vous chargez le script :

```
script-src https://app.ayrshare.com;
frame-src  https://app.ayrshare.com;
```

C'est là toute la liste. Vous n'avez besoin d'**aucune entrée `connect-src`** pour nous — nos
frames atteignent notre API depuis leur propre intérieur, pas depuis votre page — et d'**aucune
entrée pour les popups**, qui sont des fenêtres de premier niveau que votre politique ne gouverne
pas. Les deux entrées ont été mesurées : supprimer `script-src` bloque le script, et supprimer
`frame-src` bloque la frame.

## Démarrer une instance

`session` est la seule option requise. Elle est appelée **une fois par instance**, pas une fois par
mount, et doit retourner la réponse de votre backend à
[Créer une session de liaison](/docs/apis/profiles/create-link-session) avec `mode: "connect"` —
`{ sessionId, token, expiresAt }` — exactement telle qu'elle est arrivée.

```javascript Your page theme={"system"}
const connect = AyrshareConnect.init({
  session: () => fetch("/my-api/ayrshare-session").then(r => r.json()),
  appearance: { "--ayr-connect-accent": "#0B7A6C" },
  maxHeight: 800,
});
```

```javascript Your backend theme={"system"}
app.get("/my-api/ayrshare-session", async (req, res) => {
  const response = await fetch("https://api.ayrshare.com/api/profiles/link-sessions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.AYRSHARE_API_KEY}`,
      "Profile-Key": profileKeyFor(req.user),
    },
    body: JSON.stringify({ mode: "connect", origin: "https://app.example.com" }),
  });

  res.json(await response.json());
});
```

Une session de widget ne nomme **aucun** `network` — elle autorise chaque réseau permis par votre
compte, et lesquels apparaissent se décide par mount, dans votre page. Elle porte en revanche un
`origin`, qui est la seule chose qui fait que les frames s'affichent : une frame vérifie la page
qui l'intègre par rapport à cette valeur et refuse de s'afficher partout ailleurs.

<Warning>
  Créez la session sur votre serveur. L'appel nécessite votre clé API, et le token qu'il retourne
  connecte votre utilisateur à son User Profile — traitez-le comme un mot de passe.
</Warning>

Nous rappelons `session` avant l'expiration du token, donc le widget continue de fonctionner sur
une page laissée ouverte toute la journée. Un appel qui rejette, ou qui ne retourne aucun token,
est retenté deux fois de plus — après 0,5 s, puis 1 s — avant que nous abandonnions et émettions
`error`. Un rafraîchissement coûte donc au plus trois appels à votre endpoint.

| Option       | Défaut                 | Rôle                                                                              |
| ------------ | ---------------------- | --------------------------------------------------------------------------------- |
| `session`    | —                      | **Requise.** Retourne une session de liaison en mode connect.                     |
| `appearance` | nos valeurs par défaut | Tokens de design, appliqués dans chaque frame. Voir [Apparence](#appearance).     |
| `css`        | aucun                  | Une chaîne CSS appliquée dans chaque frame. Voir [CSS personnalisé](#custom-css). |
| `maxHeight`  | `600`                  | Hauteur maximale d'une frame avant qu'elle ne défile en interne.                  |

## Monter un emplacement

Un `mount` par emplacement. Demandez un réseau, plusieurs, ou tout ce que la session permet — la
granularité vous appartient, donc un emplacement peut être une seule ligne d'un tableau existant
ou un panneau unique contenant tout.

```javascript theme={"system"}
connect.mount("#instagram-slot", { network: "instagram" });
connect.mount("#some-slot", { networks: ["facebook", "tiktok", "x"] });
connect.mount("#everything");

const row = connect.mount(document.querySelector("#tall"), { maxHeight: 1200 });
row.unmount();
```

`mount` prend un sélecteur CSS ou un élément, et retourne `{ unmount, element }`. Il lève une
exception si la cible ne correspond à rien — ce qui est presque toujours un emplacement qui
n'existe pas encore, donc montez après que votre balisage est dans le document.

Chaque mount est une iframe. Elle nous rapporte sa propre hauteur et nous la redimensionnons pour
correspondre, donc votre mise en page se réorganise au fil des changements de notre contenu ;
au-delà de `maxHeight`, la frame défile en interne au lieu de déborder de votre page.
**L'emplacement le plus étroit que nous prenons en charge est de 300px.**

Les clés de réseau sont celles d'Ayrshare, et les graphies alias fonctionnent aussi : `instagram`
et `instagramapi` signifient tous deux `instagramApi`, et `x` signifie `twitter`. Une clé qui n'est
pas un réseau n'affiche aucune tuile.

## Votre propre bouton

Un client qui préfère utiliser son propre bouton plutôt qu'une de nos frames appelle `popup` à la
place. Il exécute le même flux, sur la même session, et rapporte sur les mêmes handlers.

<Warning>
  **Appelez-le directement dans le gestionnaire de clic, sans rien attendre avant.** Un navigateur
  n'autorise une popup que tant qu'il est encore en train de traiter le clic de votre utilisateur,
  et cette autorisation ne survit pas à un `await`. Rien n'a besoin d'être attendu de toute façon —
  la session a été créée à l'`init`.
</Warning>

```javascript theme={"system"}
linkedInButton.addEventListener("click", () => {
  connect.popup({ network: "linkedin" });
});
```

`popup` retourne `{ close(), network }`, et retourne **toujours** un handle — y compris après une
popup bloquée, où `close()` ne fait rien — de sorte que votre code n'a jamais à vérifier la nullité
avant d'appeler `close()`.

Tous les réseaux fonctionnent ici, y compris ceux qu'une frame termine dans son propre panneau :
Facebook affiche son écran d'explication de transfert dans la popup, Bluesky et X affichent leur
formulaire d'identifiants, et LinkedIn, Pinterest, YouTube et Google Business sortent vers le
réseau et reviennent.

Il lève une exception de manière synchrone pour les trois choses qui sont des erreurs de
programmation — pas de `network`, une instance détruite, ou une session qui n'a pas encore été
résolue. Une popup bloquée n'en fait **pas** partie : cela émet `error` avec
`reason: "popupBlocked"`, car votre utilisateur n'a rien fait de mal.

Au plus une popup est ouverte à la fois. Un second appel ferme la première et rapporte `cancelled`
avec `reason: "superseded"` sur celle-ci. Une popup ouverte par une de nos **frames** est une chose
différente et n'est jamais touchée, donc votre bouton ne peut pas annuler un flux en cours dans un
emplacement monté.

<Note>
  Savoir si la session peut lier un réseau est la réponse du **serveur**, pas celle du script. Une
  session limitée à Bluesky à laquelle on demande LinkedIn reçoit un refus affiché dans la popup et
  rapporté comme `error`. Le script vérifie seulement qu'un réseau a bien été nommé.
</Note>

## React

Le script est indépendant de tout framework, donc React ne nécessite rien de spécial de notre part —
mais quatre choses concernant son cycle de vie méritent d'être bien faites dès la première fois.

Chargez le script **une seule fois**, en dehors de votre arbre de composants. Dans Next.js, c'est
`next/script` dans votre layout racine ; dans Vite ou Create React App, c'est une balise dans
`index.html`. Le charger par composant le ré-exécute à chaque montage.

```jsx ConnectAccounts.jsx theme={"system"}
import { useEffect, useRef, useState } from "react";

export function ConnectAccounts() {
  const slot = useRef(null);
  const [linked, setLinked] = useState([]);

  useEffect(() => {
    // À l'intérieur de l'effet, pour que la ref soit attachée : mount() lève une exception si sa cible
    // n'existe pas encore, ce qui est exactement ce qui arrive si vous l'appelez pendant le rendu.
    const connect = window.AyrshareConnect.init({
      session: () => fetch("/my-api/ayrshare-session").then(r => r.json()),
      appearance: { "--ayr-connect-accent": "#0B7A6C" },
    });

    connect.mount(slot.current, { networks: ["instagram", "tiktok", "x"] });

    const stop = connect.on("success", ({ network }) => {
      setLinked(current => [...current, network]);
    });
    // `success` est l'un des dix événements que le widget rapporte. Voir Événements ci-dessous pour
    // la liste complète, y compris `state`, qui remplace le polling.

    // destroy() emporte avec lui les frames, le listener de messages et les timers.
    // Sans cela, un changement de route laisse les trois derrière.
    return () => {
      stop();
      connect.destroy();
    };
  }, []);

  return <div ref={slot} />;
}
```

<Warning>
  **Ne réutilisez jamais une instance après `destroy()`.** Une instance détruite reste détruite —
  `popup()` lève une exception sur celle-ci, et `mount()` ne la ramènera pas. Créez une nouvelle
  instance à la prochaine exécution de l'effet, ce que fait le code ci-dessus.
</Warning>

Deux conséquences de ce pattern, dont aucune n'est un bug :

* **En développement, vous verrez le callback de session se déclencher deux fois.** Le Strict Mode
  de React exécute les effets montage → démontage → montage, donc l'instance est créée, détruite et
  recréée. Le nettoyage ci-dessus rend cela sûr ; cela coûte un appel supplémentaire à votre
  backend en dev et aucun en production.
* **Gardez les dépendances de l'effet stables.** Un littéral de tableau passé directement à `mount`
  depuis un rendu parent est une nouvelle valeur à chaque fois, donc un effet qui en dépend démonte
  le widget et le reconstruit à chaque rendu. Mémoïsez-le, ou gardez-le constant comme ci-dessus.

## Événements

Abonnez-vous avec `on`, qui retourne une fonction de désabonnement. Les handlers reçoivent le
payload de l'événement et le mount dont il provient ; `off(name, handler)` fait le même travail
lorsque vous préférez nommer le handler.

```javascript theme={"system"}
const stop = connect.on("success", ({ network, displayName }, mount) => {
  refreshRow(network, displayName, mount.element);
});
stop();

connect.on("error", ({ network, code, message }) => report(code, message));
connect.destroy(); // chaque frame, listener et timer
```

Dix événements. Chacun porte un `network`, à l'exception de `ready` et du seul type d'`error` qui
ne concerne pas du tout un réseau — voir [`error` a deux sources](#error-has-two-sources)
ci-dessous.

| Événement   | Se déclenche quand                                                         | Porte aussi                                 |
| ----------- | -------------------------------------------------------------------------- | ------------------------------------------- |
| `ready`     | une frame est montée et prête                                              | —                                           |
| `click`     | votre utilisateur a cliqué sur un réseau, **dans un sens ou dans l'autre** | `action` : `"connect"` ou `"unlink"`        |
| `started`   | une tentative de liaison est en cours                                      | —                                           |
| `selection` | votre utilisateur est arrivé sur un sélecteur ou un formulaire             | `step`                                      |
| `success`   | le compte est connecté **et enregistré**                                   | `displayName` (omis quand inconnu), `refId` |
| `unlinked`  | un compte connecté a été retiré, et le retrait est enregistré              | —                                           |
| `error`     | la tentative a échoué                                                      | `code`, `message`, `reason`                 |
| `cancelled` | votre utilisateur a renoncé                                                | `reason`                                    |
| `closed`    | la popup utilisée par cette tentative s'est fermée                         | —                                           |
| `state`     | l'état de compte d'un réseau, au montage et à chaque changement            | `state`, `since`                            |

**Quatre d'entre eux sont des fins** — `success`, `unlinked`, `error` et `cancelled` — et
exactement un arrive par tentative. `closed` est un avis de cycle de vie qui suit une fin plutôt
que d'en être une.

`click` se déclenche **avant** tout travail de liaison, donc il rapporte un clic qu'une popup
bloquée ou une session morte refusera ensuite. C'est l'événement à utiliser pour vos propres
analytics ; `started` est celui qui signifie qu'une tentative est réellement en cours.

### Raisons

| Événement   | `reason`        | Signifie                                                             |
| ----------- | --------------- | -------------------------------------------------------------------- |
| `error`     | `popupBlocked`  | le navigateur a refusé d'ouvrir la popup                             |
| `cancelled` | `popupClosed`   | votre utilisateur a fermé la fenêtre à la main                       |
| `cancelled` | `scopesDenied`  | votre utilisateur a refusé une autorisation demandée par le réseau   |
| `cancelled` | `userCancelled` | votre utilisateur a renoncé, ou votre code a appelé `handle.close()` |
| `cancelled` | `superseded`    | un second appel à `popup()` a remplacé cette tentative               |

### `error` a deux sources

Une seule des deux est un échec de liaison, et elles portent des champs différents.

* Une **erreur de liaison** porte `network`, `code` et le mount dont elle provient.
* Une **erreur de session** — nous n'avons pas pu créer ou rafraîchir votre session — ne porte que
  `message`, car rien n'était en cours de liaison à ce moment-là.

Déstructurez de manière défensive : `code` et `network` sont `undefined` sur le second type.

### `state` vous épargne le polling

`state` est le canal de données plutôt qu'un rapport sur une tentative. Chaque frame en émet un par
réseau lorsqu'elle se monte, portant l'état actuel de ce réseau et le timestamp `since` depuis
lequel il le détient, et un autre à chaque changement d'état — **y compris les changements qui
prennent naissance de notre côté**, comme un token qui meurt et passe en relink-required. Vous
pouvez donc piloter toute votre UI depuis le widget sans rien poller.

Les valeurs sont le même enum que celui retourné par
[`GET /profiles` avec `include=state`](/docs/apis/profiles/get-profiles) : `linked`, `unlinked`,
`identityVerificationRequired`, `restricted`, `rateLimited`, `suspended`.

## Déliaison

Nos tuiles délient aussi bien qu'elles lient. Votre utilisateur clique sur un réseau connecté,
confirme, et le compte est retiré :

1. `click` se déclenche avec `action: "unlink"`.
2. `unlinked` se déclenche une fois le retrait enregistré.

Une déliaison qui **échoue** rapporte `error`, et une déliaison à laquelle votre utilisateur
renonce à l'étape de confirmation rapporte `cancelled`. Il n'existe pas d'événement séparé
d'échec de déliaison.

## Apparence

La feuille de style d'un client ne peut pas atteindre l'intérieur d'une frame cross-origin, donc le
style voyage sous forme de données que nous appliquons à l'intérieur. Passez `appearance` à `init`
sous forme de propriétés CSS personnalisées ; chacune que nous ne recevons pas conserve notre
valeur par défaut.

```javascript theme={"system"}
const connect = AyrshareConnect.init({
  session: getSession,
  appearance: {
    "--ayr-connect-font-family": "Inter, system-ui, sans-serif",
    "--ayr-connect-surface-bg": "#111318",
    "--ayr-connect-surface-fg": "#F2F3F7",
    "--ayr-connect-accent": "#0B7A6C",
    "--ayr-connect-accent-fg": "#FFFFFF",
    "--ayr-connect-radius": "12px",
  },
});
```

<Warning>
  **Définissez les couleurs par paires.** Un token d'arrière-plan sans son token de premier plan à
  côté est la seule manière de produire quelque chose d'illisible : définissez
  `--ayr-connect-surface-bg` sur une valeur sombre à lui seul et notre
  `--ayr-connect-surface-fg` par défaut reste bleu marine foncé. Rien ne peut inférer l'autre
  moitié pour vous.
</Warning>

Sans aucun `appearance`, chaque token garde sa valeur par défaut et une frame ressemble à ceci :

<div className="my-8">
  <Frame caption="Tokens par défaut : surfaces blanches, texte bleu marine foncé, accent indigo, rayon de 8px.">
    <img src="https://mintcdn.com/ayrshare-docs/-2tpx3mFy9vWIObd/images/multiple-users/connect-widget-appearance-default.webp?fit=max&auto=format&n=-2tpx3mFy9vWIObd&q=85&s=62f9ce7e061c8bb58132960b9e270f4a" alt="Three Ayrshare Connect tiles with the default appearance" width="920" height="528" data-path="images/multiple-users/connect-widget-appearance-default.webp" />
  </Frame>
</div>

Passez une poignée de tokens et la même frame prend votre palette. Cet exemple rend les surfaces
bleu pâle, fonce le texte et l'accent en conséquence, et arrondit un peu plus les coins :

```javascript theme={"system"}
const connect = AyrshareConnect.init({
  session: getSession,
  appearance: {
    "--ayr-connect-surface-bg": "#EFF6FF",
    "--ayr-connect-border-color": "#BFDBFE",
    "--ayr-connect-surface-fg": "#0F2A5F",
    "--ayr-connect-surface-fg-muted": "#4A6A9A",
    "--ayr-connect-accent": "#1D4ED8",
    "--ayr-connect-radius": "14px",
    "--ayr-connect-spacing": "10px",
  },
});
```

<div className="my-8">
  <Frame caption="Les trois mêmes tuiles après application des tokens ci-dessus.">
    <img src="https://mintcdn.com/ayrshare-docs/-2tpx3mFy9vWIObd/images/multiple-users/connect-widget-appearance-themed.webp?fit=max&auto=format&n=-2tpx3mFy9vWIObd&q=85&s=686d1ad900628a6d4c75dc242f1ab38c" alt="Three Ayrshare Connect tiles restyled with pale blue surfaces and a deeper blue accent" width="920" height="574" data-path="images/multiple-users/connect-widget-appearance-themed.webp" />
  </Frame>
</div>

**Il y a une seule palette et aucun préréglage clair/sombre.** Rien ne se déclenche sur
`prefers-color-scheme`, à dessein — le thème de votre page peut ne pas correspondre au système
d'exploitation de votre utilisateur, et une media query supplanterait silencieusement les couleurs
que vous avez choisies. Un tableau de bord sombre se thématise en fournissant des valeurs sombres.

Cette liste de tokens est un contrat pris en charge que nous maintenons d'une version à l'autre.

### Les tokens

| Token                              | Défaut                                                    |
| ---------------------------------- | --------------------------------------------------------- |
| `--ayr-connect-font-family`        | `Inter, "Segoe UI", system-ui, -apple-system, sans-serif` |
| `--ayr-connect-font-size`          | `16px`                                                    |
| `--ayr-connect-font-size-sm`       | `12px`                                                    |
| `--ayr-connect-label-font-weight`  | `600`                                                     |
| `--ayr-connect-status-font-size`   | `12px`                                                    |
| `--ayr-connect-status-font-weight` | `600`                                                     |
| `--ayr-connect-surface-bg`         | `#FFFFFF`                                                 |
| `--ayr-connect-surface-bg-hover`   | `#F7F7F7`                                                 |
| `--ayr-connect-surface-fg`         | `#010629`                                                 |
| `--ayr-connect-surface-fg-muted`   | `#56596F`                                                 |
| `--ayr-connect-border-color`       | `#DDDEE2`                                                 |
| `--ayr-connect-border-width`       | `1px`                                                     |
| `--ayr-connect-radius`             | `8px`                                                     |
| `--ayr-connect-spacing`            | `8px`                                                     |
| `--ayr-connect-accent`             | `#4553EE`                                                 |
| `--ayr-connect-accent-fg`          | `#FFFFFF`                                                 |
| `--ayr-connect-focus-ring-color`   | `#4553EE`                                                 |
| `--ayr-connect-focus-ring-width`   | `2px`                                                     |
| `--ayr-connect-disabled-fg`        | `#6E7185`                                                 |
| `--ayr-connect-status-radius`      | `4px`                                                     |
| `--ayr-connect-status-success-bg`  | `#EBFFF8`                                                 |
| `--ayr-connect-status-success-fg`  | `#237C5C`                                                 |
| `--ayr-connect-status-warning-bg`  | `#FFF7EF`                                                 |
| `--ayr-connect-status-warning-fg`  | `#702E00`                                                 |
| `--ayr-connect-status-critical-bg` | `#FFE5E1`                                                 |
| `--ayr-connect-status-critical-fg` | `#AD1902`                                                 |
| `--ayr-connect-status-info-bg`     | `#ECF9FF`                                                 |
| `--ayr-connect-status-info-fg`     | `#1F6686`                                                 |
| `--ayr-connect-callout-bg`         | `#FFF7EF`                                                 |
| `--ayr-connect-callout-fg`         | `#702E00`                                                 |
| `--ayr-connect-partner-name`       | `""`                                                      |
| `--ayr-connect-icon-size`          | `32px`                                                    |
| `--ayr-connect-avatar-size`        | `40px`                                                    |

Une valeur qui n'est pas du CSS valide pour son token est **ignorée, avec un avertissement dans
votre console**, plutôt qu'appliquée. C'est plus important qu'il n'y paraît : un `8` sans unité
pour `--ayr-connect-spacing` est une chaîne parfaitement innocente qui invaliderait tous les
calculs qui la lisent et effondrerait la mise en page, sans aucune erreur nulle part. Donnez une
unité aux longueurs.

### Votre propre nom sur l'écran de transfert

Avant de confier votre utilisateur à un réseau, nous affichons un court écran indiquant via qui il
se connecte. Deux hooks vous permettent de vous l'approprier, et tous deux sont stables d'une
version à l'autre.

`--ayr-connect-partner-name` définit le libellé. C'est le seul token dont la valeur est du
**texte**, donc elle doit être entre guillemets comme une chaîne CSS — une valeur sans guillemets
est invalide et n'affiche rien du tout :

```javascript theme={"system"}
AyrshareConnect.init({
  session: getSession,
  appearance: { "--ayr-connect-partner-name": "'Acme Social'" },
  css: "[data-ayr-connect-partner-mark]::after { background-image: url('https://cdn.example.com/mark.svg') }",
});
```

Le logo n'est pas un token. Nous livrons la marque comme un élément positionné et dimensionné et
vous le remplissez avec une `background-image` via `css`, comme ci-dessus — un token qui pourrait
récupérer une image depuis l'intérieur de notre document n'est pas quelque chose que nous
acceptons, donc la requête provient d'une règle que vous avez écrite plutôt que d'une valeur que
vous nous avez passée.

<Note>
  `[data-ayr-connect-partner-mark]` et `[data-ayr-connect-partner-name]` sont l'**exception** à la
  mise en garde sur le CSS personnalisé ci-dessous : ces deux sélecteurs font partie du contrat et
  nous les maintenons d'une version à l'autre.
</Note>

### CSS personnalisé

`css` prend une chaîne appliquée dans chaque frame, pour les cas que les tokens ne couvrent pas.

```javascript theme={"system"}
AyrshareConnect.init({ session: getSession, css: "button { letter-spacing: 0.01em }" });
```

<Warning>
  **Le CSS personnalisé n'est pas pris en charge d'une version à l'autre.** Ses sélecteurs ciblent
  notre balisage interne, qui change entre les releases — une règle qui fonctionne aujourd'hui peut
  cesser silencieusement de correspondre après toute mise à jour. Le contrat de tokens ci-dessus
  est la partie que nous maintenons. Épinglez une version si vous dépendez du CSS personnalisé.
</Warning>

Les popups héritent des `appearance` et `css` de l'instance exactement comme les frames, donc votre
utilisateur ne voit pas nos valeurs par défaut neutres apparaître au milieu d'un flux.

## Bon à savoir avant de livrer

<AccordionGroup>
  <Accordion title="Une popup dont la session a expiré rapporte cancelled, pas error">
    Une popup que vous avez ouverte avec `popup()` ne peut pas se voir expliquer pourquoi un token
    a été refusé — pour le dire, elle devrait faire confiance à une origine qu'elle n'a pas encore
    validée, ce que notre modèle de sécurité n'autorise pas. Une popup portant un token mort se
    ferme donc et remonte comme `cancelled` avec `reason: "popupClosed"` plutôt que comme `error`.

    En pratique, c'est rare : une popup ouverte avant un rafraîchissement silencieux continue de
    fonctionner, car elle a validé son token à son ouverture. Si vous voyez des résultats
    `popupClosed` inexpliqués, vérifiez que votre endpoint `session` retourne une session fraîche.
  </Accordion>

  <Accordion title="Une session par instance, pas une par mount">
    Quatorze mounts partagent un seul token et coûtent un appel à votre backend, pas quatorze. Si
    vous voulez des emplacements aux portées différentes — un `allowedSocial` différent sur
    certains d'entre eux — exécutez un second `init` avec sa propre session plutôt que d'attendre
    d'un mount qu'il la restreigne.
  </Accordion>

  <Accordion title="Les frames ne s'affichent que sur l'origine que vous avez déclarée">
    Chaque session porte l'`origin` sur laquelle votre page s'exécute, et une frame compare la page
    qui l'intègre à cette valeur avant d'afficher quoi que ce soit. Une frame intégrée ailleurs
    reste vide et n'envoie aucun événement. Il n'y a pas d'allowlist à enregistrer et rien à
    configurer — envoyez la bonne `origin` lorsque vous créez la session.
  </Accordion>

  <Accordion title="La hauteur est gérée pour vous, et n'est pas un événement">
    Les frames rapportent leur hauteur au script, et le script les redimensionne. Votre mise en
    page se réorganise, tout simplement. Il n'y a pas d'événement de redimensionnement auquel
    s'abonner, et rien à mesurer de votre côté.
  </Accordion>
</AccordionGroup>

## Prérequis

<ul className="custom-bullets">
  <li>
    Le **[Max Pack](/docs/additional/maxpack)**. Une session de widget est une session en mode connect,
    et en créer une sans le Max Pack retourne `code: 504`. Contactez le support si vous avez besoin
    du mode connect sur un compte qui ne l'a pas.
  </li>

  <li>
    Un **`origin`** sur chaque session — l'origine exacte sur laquelle votre page s'exécute.
    L'omettre retourne `code: 505` ; une valeur qui n'est pas une origine `https`, un schéma
    personnalisé ou `http://localhost` retourne `code: 506`.
  </li>

  <li>
    **Pas de `network`** sur la session. Ce paramètre est ce qui fait d'une session du
    [mode direct](/docs/multiple-users/connect-direct-mode) à la place, et l'URL d'une session en mode
    direct n'est pas ce que le script attend.
  </li>
</ul>

Chaque code ci-dessus figure dans la référence
[Erreurs de session de liaison](/docs/errors/errors-ayrshare#link-session-errors), avec le message exact
retourné par l'API et la marche à suivre.
