GradientShapes
BêtaComposantUn 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
- Construit sur
- FrameLoopPointerTrackerWebGLSurface
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.jsonGradientShapes 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 :
- 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. - É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 proprescolors: vérifiez alors ce contraste. - Éloignez la forme décorative du texte, avec
shapeX,shapeYetshapeSize, à 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). - 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 :
paletteoucolors,shape,shapeSize,shapeX,shapeY,shapeRotationetspin.spin={0}immobilise la forme ;bandetsplitoscillent de quelques degrés au lieu de tourner. - La lumière :
lightAngle(le côté d'où elle vient au repos),glowetspreadpour le halo,softnesspour la dissolution,contrastpour 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),randomnessetseed, puisfollow,boostetproximity,parallax,attractionetdrift. Ces six derniers se coupent à 0. Un changement deduration, derandomnessou despinne change que le rythme : rien ne saute.
Trois points de départ :
- Très sobre :
spin={0},drift={0.2},parallax={0},attraction={0}etduration={40}. Seule la lumière bouge, sous le pointeur. - Plus présent :
glow={1.4},spread={0.24}etboost={0.6}, sur une palette sombre commeduskougraphite. - Fond net :
softness={0.2}etcontrast={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
| Prop | Type | Défaut | Description |
|---|---|---|---|
palette | GradientShapesPalette | "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). |
colors | readonly string[] | null | null | Palette 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. |
lightColor | string | null | null | Teinte du halo, une couleur CSS. `null` reprend celle de la palette, ou un blanc teinté pour des `colors` libres. |
shape | GradientShapesShape | "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`. |
shapeSize | number | 0.7 | Taille 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`. |
shapeX | number | 0.5 | Centre horizontal de la forme décorative, de 0 (bord gauche) à 1 (bord droit). Pour `band` et `split`, un point par lequel passe leur bord. |
shapeY | number | 0.5 | Centre vertical de la forme décorative, de 0 (haut) à 1 (bas). |
shapeRotation | number | 0 | Orientation de départ de la forme décorative, en degrés. |
spin | number | 1.5 | Rotation 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. |
lightAngle | number | 240 | Cô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. |
glow | number | 1 | Intensité du halo, de 0 à 2. À 0, il ne reste que le bord net et la dissolution. |
spread | number | 0.16 | Portée du halo, en part de la taille de chaque forme, de 0 à 0,5. Une petite valeur le serre contre le bord. |
softness | number | 0.75 | Dissolution du côté opposé à la lumière, de 0 à 1. À 0, la forme reste nette sur tout son tour. |
contrast | number | 0.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. |
duration | number | 20 | Durée d'un cycle du flux des couleurs, en secondes, de 4 à 120. Plus long, le fond paraît plus calme. |
randomness | number | 0.5 | Irrégularité des trajectoires (couleurs, forme, dérive de la lumière), de 0 à 1. À 0, les mouvements sont réguliers. |
seed | number | 1 | Graine des trajectoires et des intérieurs des items, entier positif ou nul. Une même graine donne toujours le même fond. |
follow | number | 0.75 | Part 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. |
proximity | number | 280 | Portée du renfort de halo autour du pointeur, en pixels CSS, de 0 à 1000. À 0, seule la forme survolée est renforcée. |
boost | number | 0.3 | Renfort du halo d'une forme proche du pointeur ou qui a le focus clavier, de 0 à 1. |
parallax | number | 0.2 | Dé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. |
attraction | number | 0.15 | Attraction des couleurs du fond vers le pointeur, de 0 à 1. |
drift | number | 0.5 | Dérive lente de la lumière autour de son angle de repos, de 0 à 1 ; elle agit aussi sur écran tactile. |
pointerScope | GradientShapesPointerScope | "element" | `"element"` suit le pointeur au-dessus de la racine seulement ; `"window"` le suit dans toute la page, pour un fond plein écran. |
grain | number | 0 | Grain léger et animé, de 0 à 1. Un tramage invisible, toujours actif, évite déjà les bandes dans les dégradés. |
quality | GradientShapesQuality | "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. |
paused | boolean | false | Boolé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. |
disabled | boolean | false | Booléen : coupe le rendu WebGL et les interactions ; le repli CSS reste affiché. |
reducedMotion | ReducedMotion | "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
| Prop | Type | Défaut | Description |
|---|---|---|---|
shape | GradientShapesItemShape | "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. |
as | GradientShapesItemTag | "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 paras, 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
setAnimationsPausedde 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; enauto, 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 apaused, ou le système demande de réduire les animations. - La lumière ne suit pas le pointeur.
followvaut 0, l'appareil est tactile, ou le pointeur est hors de la racine avecpointerScope="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-pathetmask-image) s'affiche ; il demande les unitéscqmin(Chrome 105, Safari 16, Firefox 110). - Un
border-radiuselliptique 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.