Aller au contenu
O-UI

Recherche

Trouvez un composant ou une page de documentation.

GradientShapes

BêtaComposant

Un fond en dégradé animé où des formes douces s’éclairent en contre-jour ; la lumière suit le pointeur et les formes peuvent porter du contenu.

Frameworks
ReactVueVanilla
Technologies
WebGLCSS
Déclenché par
PointeurContinu
Mis à jour le
Paramètres

GradientShapes peint un fond en dégradé qui coule lentement, et des formes douces éclairées par-derrière : du côté de la lumière, un bord net et un halo pâle ; à l'opposé, son contour s'estompe. Le pointeur devient la source de lumière. Des éléments de votre page, comme des cartes, des badges ou un formulaire, deviennent eux-mêmes des formes éclairées qui gardent leur contenu. Utilisez-le pour un hero, une section d'accroche ou un écran d'accueil ; pour un fond qui doit rester parfaitement immobile, un dégradé CSS suffit.

Installation

Installez l'élément et ses dépendances en une commande :

pnpm dlx shadcn@latest add https://o-ui.hd987.dev/r/gradient-shapes.json

GradientShapes n'a aucune dépendance npm. Il installe avec lui trois primitives d'O-UI : WebGLSurface, qui partage un seul contexte WebGL entre tous les éléments de la page, FrameLoop, la boucle d'animation et la pause commune, et PointerTracker, le suivi du pointeur. Les prérequis communs à tous les composants (Tailwind CSS v4, React 19 ou Vue 3.5) sont décrits dans Installation.

Utilisation

GradientShapes est le conteneur de votre section : son contenu passe devant le fond. Sans aucune prop, il dessine un cercle éclairé sur la palette aurora.

import { GradientShapes } from "@/components/o-ui/gradient-shapes/gradient-shapes";

export function Hero() {
  return (
    <GradientShapes shapeX={0.7} className="flex min-h-[560px] items-center px-8">
      <h1 className="max-w-lg text-5xl font-semibold text-(--gradient-shapes-foreground)">
        Design in the light
      </h1>
    </GradientShapes>
  );
}

Quelques règles font la différence :

  1. Donnez une taille à GradientShapes. Le fond remplit sa boîte : une hauteur minimale (min-h-[560px]) ou le contenu lui-même la fixent. Les tailles de la forme décorative sont relatives au plus petit côté de cette boîte.
  2. Écrivez le texte avec text-(--gradient-shapes-foreground). La racine pose cette variable d'après la palette : du blanc ou un noir bleuté, celui des deux qui garde le meilleur contraste sur toutes ses couleurs. Les palettes nommées tiennent au moins 4,5:1 sur chacune de leurs couleurs, le niveau AA d'un texte courant, à distance des formes. La variable est calculée aussi pour vos propres colors : vérifiez alors ce contraste.
  3. Éloignez la forme décorative du texte, avec shapeX, shapeY et shapeSize, à toutes les largeurs : son bord éclairé et son halo sont presque blancs. Sur mobile, gardez-lui de la place sous le texte (une marge basse, comme dans le hero ci-dessous).
  4. Pour qu'un élément devienne une forme éclairée, utilisez GradientShapesItem. Il peut être n'importe où dans GradientShapes ; c'est un vrai élément HTML, qui garde son contenu, son rôle et son focus.

Des formes qui portent du contenu

GradientShapesItem rend un élément (div par défaut, ou la balise de as) derrière lequel le fond dessine une forme éclairée, alignée au pixel près. Avec shape="auto", la forme est la boîte de l'élément et son border-radius : la classe rounded-3xl suffit.

import { GradientShapes, GradientShapesItem } from "@/components/o-ui/gradient-shapes/gradient-shapes";

export function Features() {
  return (
    <GradientShapes palette="dusk" shape="none" className="px-8 py-16">
      <ul className="grid gap-6 sm:grid-cols-3 text-(--gradient-shapes-foreground)">
        <GradientShapesItem as="li" className="rounded-3xl p-7">
          <h3 className="font-semibold">Realtime sync</h3>
          <p>Every edit lands on every device.</p>
        </GradientShapesItem>
        <GradientShapesItem as="li" shape="hexagon" className="grid size-32 place-items-center">
          42
        </GradientShapesItem>
      </ul>
    </GradientShapes>
  );
}
  • La position est lue à chaque image dessinée. Un item déplacé par la mise en page, une transition CSS ou un changement de classe garde sa forme alignée, sans rien à appeler.
  • Les formes autres que auto (circle, ellipse, hexagon, flower, diamond, star, parallelogram) s'inscrivent dans la boîte de l'élément. Donnez-lui une boîte carrée (size-32) pour une forme régulière, et des marges intérieures pour que le contenu reste dans la forme.
  • Le côté opposé à la lumière s'estompe. Pour des cartes chargées de texte, baissez softness (vers 0,45) : leur bord éloigné reste plus net.

Exemples

Hero produit

Le rendu par défaut, la forme éloignée du texte : à droite sur grand écran, sous le texte jusqu'aux tablettes, grâce à shapeX, shapeY et une marge basse. Le titre et le texte prennent la couleur lisible de la palette, sans opacité réduite.

Cartes de fonctionnalités

Chaque carte est un GradientShapesItem en as="li", dont la forme vient de son rounded-3xl. Sans forme décorative (shape="none"), la lumière se tourne vers la carte survolée, ou vers celle qui contient le focus clavier. softness={0.45} garde les cartes lisibles jusqu'à leur bord éloigné.

Étapes en formes

Trois items de formes différentes, chacun dans une boîte carrée de 112 pixels. Le numéro est décoratif (aria-hidden) : la liste ordonnée porte déjà l'ordre. La palette citrus, claire, appelle un texte foncé, que --gradient-shapes-foreground donne d'elle-même.

Connexion graphite

La palette graphite donne une scène monochrome à lumière chaude, et la forme band une bande oblique qui traverse le cadre. Le formulaire est dans un item as="section" : quand un champ reçoit le focus clavier, la lumière vient vers la carte.

Fin de page aux couleurs de marque

colors remplace la palette par quatre couleurs profondes de la marque, et lightColor teinte le halo. La couleur du texte est déduite de ces couleurs : choisissez-les assez sombres, ou assez claires, pour qu'un même texte se lise sur toutes. Le demi-plan split traverse le bloc d'un bord à l'autre ; shapeX règle où passe son bord.

Personnalisation

Les réglages se regroupent en trois familles.

  • Le décor : palette ou colors, shape, shapeSize, shapeX, shapeY, shapeRotation et spin. spin={0} immobilise la forme ; band et split oscillent de quelques degrés au lieu de tourner.
  • La lumière : lightAngle (le côté d'où elle vient au repos), glow et spread pour le halo, softness pour la dissolution, contrast pour l'écart entre l'intérieur des formes et le fond.
  • Le mouvement et le pointeur : duration (un cycle des couleurs, 20 s par défaut), randomness et seed, puis follow, boost et proximity, parallax, attraction et drift. Ces six derniers se coupent à 0. Un changement de duration, de randomness ou de spin ne change que le rythme : rien ne saute.

Trois points de départ :

  • Très sobre : spin={0}, drift={0.2}, parallax={0}, attraction={0} et duration={40}. Seule la lumière bouge, sous le pointeur.
  • Plus présent : glow={1.4}, spread={0.24} et boost={0.6}, sur une palette sombre comme dusk ou graphite.
  • Fond net : softness={0.2} et contrast={0.8}, pour des formes franches, plus graphiques.

Toute la page. Pour un fond plein écran derrière une page qui défile, pointerScope="window" suit le pointeur partout, même au-dessus d'éléments posés devant le fond.

Aux couleurs de votre site

colors remplace la palette par vos propres couleurs : de 2 à 5 couleurs CSS (hex, rgb(), hsl(), oklab() ou oklch()), par exemple la couleur principale et la couleur secondaire d'une marque. Le fond, l'intérieur des formes et la couleur de texte --gradient-shapes-foreground en sont déduits. lightColor teinte le halo ; sans elle, la lumière est un blanc légèrement teinté par vos couleurs. Dans le playground, choisissez la palette « Personnalisée » pour les essayer, puis copiez le code.

<GradientShapes colors={["#0f766e", "#0e7490", "#134e4a"]} lightColor="#d9f99d" />

Le texte se lit d'autant mieux que vos couleurs sont proches en clarté : toutes assez sombres pour un texte blanc, ou toutes assez claires pour un texte foncé. Vérifiez son contraste sur votre composition.

Intégration

Next.js et Nuxt

Le rendu serveur produit la racine, le repli CSS (le dégradé, la forme décorative et les surfaces des items) et la variable de texte. Le canvas apparaît en fondu dès que la première image est prête.

  • Next.js : GradientShapes est un composant client, utilisable dans un Server Component.
  • Nuxt : aucun <ClientOnly> n'est nécessaire. Importez les composants explicitement, comme dans les exemples : l'import automatique de Nuxt préfixe leur nom par leur dossier.

Mettre en pause toutes les animations

Le fond bouge sans fin. Pour la WCAG 2.2.2, la page doit permettre de mettre ce mouvement en pause. La pause commune de FrameLoop, installé avec GradientShapes, fige tous les fonds de la page et les autres animations continues d'O-UI : un seul interrupteur suffit.

"use client";

import { useAnimationsPaused } from "@/components/o-ui/frame-loop/frame-loop";
import { setAnimationsPaused } from "@/components/o-ui/frame-loop/frame-loop-core";

export function MotionToggle() {
  const paused = useAnimationsPaused();
  return (
    <button
      type="button"
      aria-pressed={paused}
      onClick={() => {
        setAnimationsPaused(!paused);
      }}
    >
      Pause animations
    </button>
  );
}

En pause, les couleurs et la forme décorative s'arrêtent ; la lumière répond encore au pointeur et au focus. Pour un seul fond, utilisez la prop paused.

Accéder à l'élément

La ref donne la racine de GradientShapes. En Vue, la ref posée sur le composant expose { el }. GradientShapes n'a pas d'API impérative : pilotez-le par ses props.

API

PropTypeDéfautDescription
paletteGradientShapesPalette"aurora"Ambiance de couleurs : celles du fond et de l'intérieur des formes, la couleur de la lumière et la couleur de texte conseillée. 11 palettes, de `aurora` (cyan et violet) à `graphite` (gris à lumière chaude).
colorsreadonly string[] | nullnullPalette libre de 2 à 5 couleurs CSS (hex, `rgb()`, `hsl()`, `oklab()`, `oklch()`), par exemple celles de votre marque ; elle remplace `palette`. Une couleur invalide est ignorée ; avec moins de 2 couleurs valides, `palette` s'applique.
lightColorstring | nullnullTeinte du halo, une couleur CSS. `null` reprend celle de la palette, ou un blanc teinté pour des `colors` libres.
shapeGradientShapesShape"circle"Forme décorative : `circle`, `ellipse`, `rounded`, `hexagon`, `flower`, `diamond`, `star`, `parallelogram`, `band` (une bande qui traverse le cadre), `split` (un demi-plan) ou `none`.
shapeSizenumber0.7Taille de la forme décorative, en part du plus petit côté de la racine, de 0,1 à 1,5. Pour `band`, la largeur de la bande ; sans effet sur `split`.
shapeXnumber0.5Centre horizontal de la forme décorative, de 0 (bord gauche) à 1 (bord droit). Pour `band` et `split`, un point par lequel passe leur bord.
shapeYnumber0.5Centre vertical de la forme décorative, de 0 (haut) à 1 (bas).
shapeRotationnumber0Orientation de départ de la forme décorative, en degrés.
spinnumber1.5Rotation continue de la forme décorative, en degrés par seconde, de −30 à 30. À 0, elle ne tourne pas. `band` et `split` oscillent de quelques degrés.
lightAnglenumber240Côté d'où vient la lumière au repos, en degrés : 0 en haut, 90 à droite, dans le sens horaire. 240 la place à gauche, un peu en bas.
glownumber1Intensité du halo, de 0 à 2. À 0, il ne reste que le bord net et la dissolution.
spreadnumber0.16Portée du halo, en part de la taille de chaque forme, de 0 à 0,5. Une petite valeur le serre contre le bord.
softnessnumber0.75Dissolution du côté opposé à la lumière, de 0 à 1. À 0, la forme reste nette sur tout son tour.
contrastnumber0.5Écart entre l'intérieur des formes et le fond, de 0 à 1. À 0, une forme ne se distingue plus que par son bord et son halo.
durationnumber20Durée d'un cycle du flux des couleurs, en secondes, de 4 à 120. Plus long, le fond paraît plus calme.
randomnessnumber0.5Irrégularité des trajectoires (couleurs, forme, dérive de la lumière), de 0 à 1. À 0, les mouvements sont réguliers.
seednumber1Graine des trajectoires et des intérieurs des items, entier positif ou nul. Une même graine donne toujours le même fond.
follownumber0.75Part de rotation de la lumière vers le pointeur, et vers l'item qui a le focus clavier, de 0 à 1. À 0, la lumière reste au repos. Elle glisse autour des formes, sans saut.
proximitynumber280Portée du renfort de halo autour du pointeur, en pixels CSS, de 0 à 1000. À 0, seule la forme survolée est renforcée.
boostnumber0.3Renfort du halo d'une forme proche du pointeur ou qui a le focus clavier, de 0 à 1.
parallaxnumber0.2Décalage de profondeur de la forme décorative et des intérieurs quand le pointeur bouge, de 0 à 1. Les items, eux, ne bougent jamais.
attractionnumber0.15Attraction des couleurs du fond vers le pointeur, de 0 à 1.
driftnumber0.5Dérive lente de la lumière autour de son angle de repos, de 0 à 1 ; elle agit aussi sur écran tactile.
pointerScopeGradientShapesPointerScope"element"`"element"` suit le pointeur au-dessus de la racine seulement ; `"window"` le suit dans toute la page, pour un fond plein écran.
grainnumber0Grain léger et animé, de 0 à 1. Un tramage invisible, toujours actif, évite déjà les bandes dans les dégradés.
qualityGradientShapesQuality"auto"`"auto"` règle la densité de rendu d'après la taille et l'écran ; `"low"` dessine à 0,75 pixel par pixel CSS, `"medium"` à 1, `"high"` jusqu'à 2.
pausedbooleanfalseBooléen : fige le flux des couleurs et la forme décorative de ce fond. La lumière répond encore au pointeur et au focus. Pour toute la page, voir `setAnimationsPaused` de FrameLoop.
disabledbooleanfalseBooléen : coupe le rendu WebGL et les interactions ; le repli CSS reste affiché.
reducedMotionReducedMotion"user"`"user"` suit le réglage du système, `"always"` donne une image fixe dont la lumière répond encore au pointeur, `"never"` garde le mouvement : seulement quand la personne a réactivé les animations dans les réglages de votre site.

Les nombres hors plage sont ramenés dans la plage. Une valeur invalide garde la valeur courante, ou la valeur par défaut à la création. Les adaptateurs transmettent les attributs natifs, ainsi que className en React et class en Vue ; la racine a relative isolate overflow-hidden.

GradientShapesItem

PropTypeDéfautDescription
shapeGradientShapesItemShape"auto"Forme de l'item. `auto` dessine la boîte de l'élément avec son `border-radius` calculé ; `circle`, `ellipse`, `hexagon`, `flower`, `diamond`, `star` et `parallelogram` s'inscrivent dans la boîte.
asGradientShapesItemTag"div"Élément rendu : `div` (par défaut), `article`, `section`, `aside`, `header`, `footer`, `figure`, `li`, `a`, `button` ou `span`. Son rôle et son nom accessible restent les siens.

L'item rend les attributs data-gradient-shapes-item et data-shape ; en vanilla, un élément qui les porte devient une forme de la même façon. Il a relative isolate et reçoit les attributs natifs. Un fond dessine au plus douze formes, la forme décorative comprise : au-delà, les items gardent leur repli CSS.

Accessibilité

Ce que fait GradientShapes

  • Le canvas et les surfaces du repli sont décoratifs (aria-hidden) et ne captent pas le pointeur. Le contenu garde sa sémantique ; un item rend l'élément choisi par as, avec son rôle et son nom.
  • Au focus clavier dans un item, la lumière se tourne vers lui et son halo se renforce. L'indicateur de focus de l'élément n'est jamais modifié.
  • Le fond s'arrête hors écran et dans un onglet masqué, et reprend sans saut.
  • Le pointeur n'agit qu'avec une souris ou un pavé tactile. Sur écran tactile, rien ne suit le doigt : la lumière dérive lentement, les couleurs coulent.
  • Aucun clignotement : la lumière et les couleurs varient lentement, sans saut de luminosité.

Ce que vous devez faire

  • Permettre de mettre le mouvement en pause (WCAG 2.2.2) : un seul interrupteur pour toute la page, avec setAnimationsPaused de FrameLoop (voir Mettre en pause). Le réglage de mouvement réduit du système ne le remplace pas.
  • Écrire le texte avec --gradient-shapes-foreground, sans opacité réduite, et vérifier son contraste sur votre composition, à toutes les largeurs : les outils d'audit ne mesurent pas un texte posé sur un canvas. Le bord éclairé et le halo sont presque blancs : gardez le texte à distance des formes.
  • Garder l'indicateur de focus des éléments interactifs : la lumière montre la carte qui contient le focus, l'anneau montre l'élément.

Mouvement réduit. L'image devient fixe : les couleurs ne coulent plus, la forme décorative ne tourne plus, et la parallaxe, l'attraction et la dérive s'arrêtent. La lumière suit encore le pointeur et le focus, plus vite : c'est un changement d'éclairage, sans déplacement. Un changement du réglage système s'applique en direct.

Contraste élevé. En mode forced-colors, le canvas et les surfaces du repli sont masqués : le contenu repose sur les couleurs du système.

Performances

  • Le cœur et ses fichiers annexes pèsent environ 17 kB minifiés et compressés (42 kB minifiés, dont 11 kB de shaders), sans dépendance.
  • Un seul dessin plein cadre par image : les bords, la dissolution et le halo sont calculés directement, sans passe de flou. La densité de rendu suit quality ; en auto, elle baisse vers 1× sur une grande surface.
  • Tous les éléments WebGL d'O-UI de la page partagent un seul contexte, ceux de WebGLSurface, et une seule boucle d'animation, celle de FrameLoop.
  • Le flux des couleurs, lent, est dessiné à 30 images par seconde ; une interaction (pointeur, focus, transition d'un item) est dessinée à chaque image. Un fond en pause, sous mouvement réduit, hors écran ou dans un onglet masqué ne dessine rien tant que rien ne change.

Problèmes fréquents

  • Le fond ne bouge pas. La page a appelé setAnimationsPaused(true), le fond a paused, ou le système demande de réduire les animations.
  • La lumière ne suit pas le pointeur. follow vaut 0, l'appareil est tactile, ou le pointeur est hors de la racine avec pointerScope="element".
  • Le fond reste un simple dégradé. WebGL2 est indisponible ou le contexte a été perdu : le repli CSS s'affiche. La racine porte alors data-renderer="css" (et "webgl" quand le canvas dessine).
  • Un item n'a pas de forme. Il est hors de GradientShapes, ou dans un autre GradientShapes imbriqué : un item appartient au fond le plus proche. Au-delà de douze formes, les suivants gardent leur repli CSS.
  • Le texte d'une carte se perd vers son bord. Le côté opposé à la lumière s'estompe : baissez softness.

Notes et limites

  • Le rendu demande WebGL2. Sans lui, le repli CSS (dégradés, forme décorative et surfaces des items, en clip-path et mask-image) s'affiche ; il demande les unités cqmin (Chrome 105, Safari 16, Firefox 110).
  • Un border-radius elliptique sur un item est approché par le plus petit de ses deux rayons.
  • La forme d'un item suit sa boîte, pas ses transformations : une rotation CSS de l'item n'est pas reproduite.
  • Sous mouvement réduit ou en pause, un item déplacé sans redimensionnement ni changement de classe ou de style (par un texte voisin qui change, par exemple) garde sa forme à l'ancienne place jusqu'au prochain changement de taille, de classe, de style ou de prop.
  • Des fonds imbriqués suivent chacun leur propre état : un GradientShapes placé dans un autre garde son repli CSS tant qu'il ne dessine pas.
  • La racine a overflow: hidden : un menu déroulant qui doit en sortir s'affiche dans un portail.