Next JS: Implémenter une page web responsive avec Tailwind

Comment utiliser Tailwind et Next JS pour créer une page web responsive

Project Next JS Table des matières

A présent nous allons crée une page responsive avec Tailwind et Next JS.

Sur cette page nous allons mettre deux sections pour illustrer comment faire. Une section présentera le but du site, la proposition principale de la page. C’est ce qu’on appelle une “Hero Section”. Nous créerons également une section qui présente des fonctionnalités.

Si vous avez suivi le projet avec Next JS, nous allons nous rendre sur le fichier index.tsx à la racine du dossier pages. Juste à l’intérieur du <main>on va remplacer notre H1 par un élément <section>, et on va lui attribuer un className="hero".

Ici à l’intérieur nous allons créer une nouvelle <div>, à laquelle on va attribuer un className="hero-content"

<main>
  <section className="hero">
    <div className="hero-content">
    </div>
  </section>
<main>

Et à présent on va remplir tout ça. On va commencer par créer un h1. Il faut savoir que de base un h1n’a pas de style particulier qui lui est appliqué, et c’est complètement volontaire. Le h1sert surtout pour des raisons de référencement. Et du coup ici la présentation est découplée de la sémantique.

Ici il se trouve que le <h1>correspond bien à notre titre, donc dans le classNamedu <h1>on va ajouter text-5xlqui détermine la taille du texte, et on va changer l’épaisseur de texte avec un font-bold. Tout ca ce sont des classes atomiques de Tailwind. Comme vous pouvez voir si vous inspectez ce qui sort, ces classes définissent chacune une valeur en CSS.

Dans ce <h1> on va mettre un texte simple, par example: “Have fun tracking your goals using OKR”. On peut évidemment faire beaucoup mieux. On va rajouter un sous-titre avec un h2, par exemple: “OKR is a proven methodology used by Intel and Google to exceed expectations.” Et pour finir on va rajouter un bouton.

Ici on va simplement créer un <span>dans lequel on va écrire “Learn more”. Et DaisyUI dispo aussi de composant Bouton simple à utiliser et bien documenté. Ici pour donner une apparence de bouton à notre <span>, on lui rajoute un className="btn btn-primary"puisqu’on veut que notre bouton prenne la couleur primaire de notre site.

Nous allons à présent mettre en place l’image, mais tout d’abord il nous manque une petite étape. Il nous faut créer une <div>qui englobe les trois éléments que nous avons codés. Cette <div>nous permettra de positionner ensemble les trois éléments.

 <div>
   <h1 className="text-5xl font-bold">
     Have fun tracking your goals using OKR
   </h1>
   <h2>
     OKR is a proven methology used by Intel and Google to beat
     expectations
    </h2>
    <span className="btn btn-primary">
      Learn more
    </span>
</div>

Passons à présent à l’image


Alors je ne suis pas particulièrement doué en graphisme, donc j’ai fabriqué une image avec MidJourney qui pouvait correspondre à ce que j'avais besoin. On va simplement mettre cette image dans le dossier public, et l’appeler mountain_climb. Il se trouve que MidJourney nous a fourni des fichiers PNG donc mon image s’appelle mountain_climb.png_.

Bon il faut savoir que le format PNG est rarement optimal, et si on veut que notre site web se charge vite il faut optimiser les images qui s’y trouvent. Heureusement dans Next JS on dispose d’un composant qui fait le travail pour nous. Ce composant s’appelle <Image>, et il imite la syntaxe de l’élément HTML <img>.

Pour bien fonctionner ce composant a besoin de connaître les dimensions de l’image d’origine. Rien de plus simple, on peut aller les lire via notre OS; ici sur Mac en inspectant l’image. Sur PC il faut regarder les propriétés. C’est important de souligner qu’il s’agit ici des dimensions du fichier image, pas des dimensions qu’on souhaite afficher à l’écran.

Ici dans mon cas l’image fait 768 pixels de large et 512 pixels de haut.

Nous sommes donc armés pour commencer à mettre en place l’image. On va donc commencer par taper :

<Image src="/mountain_climb.png" />  

On voit que TypeScript se plain qu’il manque l’attribut alt, et c’est vrai qu’en termes de SEO et d’accessibilité il faut toujours mettre un descriptif. Donc on va préciser : alt="man climbing up a mountain. Autrement dit un homme qui escalate une montagne.

Pendant qu’on y est, on va aussi préciser les dimensions de l’image, donc width={768}et height={512}.

<Image src="/mountain_climb.png" width={768} height={512} alt="man climbing up a mountain />  

Voyons ce que ça donne. On a bien l’image à gauche avec notre titre, sous-titre et CTA à droite. En l’état, les titres me semblent trop collés ensemble et le sous-titre me semble un peu petit.

Donc on va rajouter de l’espacement. On va commencer par rajouter un py-3au titre principal en h1. Ensuite, on copie colle les classes du h1sur le h2, et on change la taille de text-5xlà text-3xl

Si on regarde ce que ça donne, il y a plusieurs problèmes. L’espacement est trop gros, et le sous titre prend trop de place. On va donc descendre le text-3xlà juste text-xl. On va aussi enlever font-bold. Et pour ce qui est de l’espace entre le h1et le h2, c’est logique puisque le padding horizontal se met de chaque coté de chaque élément, donc on a un double paddings entre le h1 et le h2. Du coup on va just change le padding du h2à pb-3. Le bsignifie bottomdonc à présent le h2n’a plus un padding au dessus, juste en dessous.

Et si on regarde le résultat final, c’est effectivement bien plus équilibré. Je pense qu’on pourrait aussi rajouter un peu de padding sur l’image, disons un px-3.

Par contre si on s’amuse à changer la largeur du navigateur, on voit que c’est pas du tout adapté pour les écrans mobiles, c’est pas “responsif”. Il faut qu’on corrige tout ça.

Alors c’est l’occasion de parler de comment Tailwind gère la “responsivité”, le fait de gérer les écrans de taille différente. Et ici je veux donc préciser deux choses.

1/ La première chose à dire, c’est que Tailwind, comme tous les autres frameworks CSS dignes de ce nom, et “mobile-first”. Qu’est-ce que ca veut dire ?

Et bien ça signifie que quand on construit la page, on définit le comportement mobile comme étant la règle, et le comportement sur les résolutions plus grandes comme étant l’exception.

Pour prendre le cas de notre “Hero Section” ici, on va dire : on va avoir par défaut une présentation en colonne, avec l’image au dessus, et nos trois éléments textuels en dessous. Et à partir d’une certaine largeur, par exemple sur tablette, on change le comportement pour que ces éléments soient côte à côte.

Donc on définit le comportement mobile comme étant le comportement normal, et la mise en page pour les dimensions plus grandes comme étant l’exception. ET vu qu’au moins 60% du trafic sur internet est via mobile, ça se tient bien.

2/ la deuxième chose que je veux mentionner ici, c’est la nomenclature de Tailwind CSS pour gérer la responsivité. C’est en réalité plus large que ça, c’est la façon dont tailwind gère les variantes des classes.

Et pour appeler ces variantes, dans Tailwind, on rajoute un préfix, et un signe “deux points”. Donc par exemple pour préciser un comportement sur le hover, on va écrire “hover:” puis le nom de la classe qu’on veut activer sur le hover. De la même façon, pour gérer le “dark mode” directement dans Tailwind, il suffit de créer des classes avec le préfix “dark:”.

Maintenant, prenons l’exemple du sujet qui nous intéresse ici, de la gestion de la “responsivité”. Tailwind dispose par défaut de cinq breakpoints, qui sont:

  • sm comme Small, qui est à 640 pixels de large
  • md comme Medium, qui est à 768 pixels
  • lg comme Large qui est à 1024 pixels,
  • xl qui est à 1280 pixels, et
  • 2xlqui est à 1536 pixels.
    Tout ça étant customisable évidemment.

Dans Tailwind on a les classes w-full, et w-1/2qui signifient une largeur de 100% et de 50%. Imaginons qu’on veuille qu’un élément ait sur mobile une largeur de 100%, et à partir d’une taille d’écran de tablette une largeur de 50%. Dans ce cas on attribue à l’élément les classes w-fullet md:w-1/2. Autrement dit : par défaut une pleine largeur, et à partir du “breakpoint md”, une largeur de 50%. Et c’est ca qu’on va utiliser ici pour rendre le site responsif

Donc, c’est exactement ce qu’on va utiliser sur notre hero section. Donc là on a l'image et le contenu texte. On va raisonner en partant en mobile. En mobile, l’image et le contenu texte font chacun occuper toute la largeur de l’écran, et le texte va se situer en dessous de l’image. Et à partir de la taille d’écran médium, c’est à dire à peu près une tablette, ils vont chacun occuper une demi largeur. Donc on leur applique à chacun les classes w-fullet et md:w-1/2.

  <Image className="w-full md:w-1/2" /*...*/ />
  <div className="w-full md:w-1/2" >
    {/*...*/}
  </div>

Par contre ça ne suffit pas, puisque ces deux éléments sont contenus dans un element qui applique du flex en ligne. Et donc pour avoir un alignement en colonne en mobile, et en ligne en desktop, on applique le meme principe. Autrement dit, va sur l’élément qui a la classe hero-contentset on lui rajoute une classe flex-colpour lui dire de se mettre en colonne par défaut en mobile, et md:flex-rowpour lui dire d’aligner les éléments en ligne à partir du “breakpoint” md.

<>

Section 3 colonnes

A présent ce que nous allons faire, c’est créer une deuxième section qui détaille tous les avantages, et on va utiliser une structure classique sur 3 colonnes.

Pour ça on va commencer par créer un élément section. On va commencer par mettre un titre dans notre section, Cette fois ci, on on va le créer en h3, puisque on a déjà défini notre h1et notre h2. Et dedans on va simplement écrire “Our features”, nos fonctionnalités.

<h3>Our features</h3>

En dessous de ce h3on va créer une <div>qui va contenir nos trois colonnes qui vont vanter les mérites de notre site.

Mais avant de remplir cette div, nous allons changer sa taille du h3 et rajouter un peu d’espace en dessous, donc nous allons lui donner les classes text-xlet pb-3.

Si on regarde ce que ça donne, on voit que tout ça est aligné à gauche. On va donc par dire à notre <section> de centrer le texte qu’elle contient, et pour ca on lui rajoute la classe text-center. Et on lui demande aussi de laisser un peu de marge avec la section précédente, donc on lui donne un mt-4. Au passage si vous vous demandez pourquoi je mets parfois 3, parfois 4.. en réalité pour le moment je prends un peu au hazard et j’ajuste en fonction de ce que donne le résultat, visuellement. Dans la vraie vie c’est le genre de chose qu’on détermine avec un UX designer, mais on va faire sans.

Maintenant passons à nos trois colonnes. Pour voir ce que ça donne nous allons créer trois <div>. Dedans nous allons simplement mettre “Argument 1”, “Argument 2”, et “Argument 3”. On verra plus tard pour mettre quelque chose de plus convainquant.

Si on regarde ce que ça donne, on voit que les 3 colonnes sont pour le moment l’un au dessus de l’autre. Ce n’est pas encore tout à fait le résultat recherché.

La première chose à faire, donc, c’est dire à la <div>qui contient tout ça qu’on souhaite que les éléments intérieurs soient alignés et que la ``

``` prenne la largeur de l’écran.

On lui donne donc les classes : flex w-fullEt on va rajouter une troisième classe qui s’appelle justify-aroundqui permet de définir comment les éléments sont répartis sur la ligne.

Et si on regarde ce que ça donne, justify-aroundenvoie vraiment les éléments contre le bord, puisque ça équilibre les espaces entre les éléments. Souvent c’est le résultat souhaité. Ca permet que tous les bords des éléments soient bien alignés sur toute la page. Ici dans notre cas… je trouve ça moche, on va plutôt prendre justify-between, qui équilibre aussi les espace à l’extérieur des éléments. Et si on regarde le résultat c’est plus équilibré visuellement.

Le composant FeatureCard

A présent nous allons transformer l’élément qui contient les arguments en composants. Donc dans le dossier components/on va créer un nouveau fichier, et on va l’appeler FeatureCard.tsx.

Dans ce fichier on va créer un composant avec le même nom, et on va bien préciser qu’on veut l’exporter et que c’est un composant fonctionnel, et donc de type “React.FC”

export const FeatureCard:React.FC

Ce composant sera une fonction, et on va écrire cette fonction avec la notation “grosse flèche” :

export const FeatureCard:React.FC = () => {
}

Et pour le moment cette fonction va simplement retourner une <div>dans lequel on écrit “Card”.

export const FeatureCard:React.FC = () => {
  return <div>Card</div>
}

Maintenant on va construire l’intérieur, et on veut y mettre un titre, une image et peut être un descriptif.

Donc on va commencer par préciser l’interface de notre composant, cette à dire le type de données qu’on veut recevoir.

Pour ça on va donc créer l’interface, qu’on va appeler FeatureCardInterface.

interface FeatureCardInterface {
}

Dans cette interface on va définir trois champs, à savoir un title, qu’on va définir comme une string, un champ descriptionqui sera aussi une string, et un champ imagequi sera le chemin vers l’image et qui sera donc aussi une string.

interface FeatureCardInterface {
  title: string;
  description: string; 
  image :string;
}

A présent il faut spécifier au composant qu’il attend ce type de données en entrées, donc on va rajouter l’interface sur la signature du composant

FeatureCard:React.FC<FeatureCardInterface>

Et du coup on peut rajouter les paramètres qu’on vient de définir dans l’interface, dans la fonction. Donc dans les parenthèses on ouvre des accolades, pour dire que c’est un object passé en paramètre. Et puis on y précise les trois paramètres qu’on a définis :

export const FeatureCard:React.FC = ({title, description, image})

A présent à l’intérieur de la <div>du composant, on va commencer par créer un élément <h4>, histoire de respecter la hiérarchie des titres. Et dans ce <h4>nous allons simplement metre le titleentre accolades:

<h4>{title}</h4>

En dessous nous allons mettre l’image, en utilisant le composant <Image>de NextJS. Et on va lui donner un attribut srcauquel on assigne la valeur du champ image en paramètre:

<Image src={image} /> 

On va lui attribuer un champ alt, on n’a pas vraiment de paramètre qui convienne bien mais on va lui mettre le paramètre title.

<Image src={image} alt={title} /> 

Et j’ai pas encore l’image sous la main mais on a besoin de préciser un width et un height sur l’image comme on avait vu précédemment. Pour le moment on va rester sur les memes dimensions que l’image précédente, avec un width de 768 pixels et un heightde 512.

<Image src={image} alt={title} width={768} height={512}/> 

Il faut aussi s’assurer que le composant <Image>a bien été importé, donc on va le chercher dans next/image:

import Image from "next/image";

Finalement on rajoute un <span>dans lequel on met la description.

<span>{description}</span>

Du coup notre FeatureCard ressemble à ceci :

export const FeatureCard:React.FC<FeatureCardInterface> = ({title, description, image, className}) => {
  return <div>
    <h4>{title}</h4>
    <Image src={image} alt={title} width="512"  />
    <span>{description}</span>
  </div>
}

A présent retournons dans le fichier index.tsx. Ici, nous allons remplacer les trois <div>avec Argument 1 à 3 par le composant que nous venons de créer.

On commence à taper <FeatureCard />Ensuite on lui attribue un title, ici on va simplement mettre “Argument 1”. On va aussi lui préciser son champ description, et on va mettre pour le moment un truc bateau genre “This is really cool”. Et finalement il faut préciser le champ image. Comme j’ai pas tellement d’autre image sous la main, on va simplement lui passer celle qu’on a déjà, donc celle qui s’appelle mountain_climb.png_:

    <FeatureCard title="Argument 1" description="This is really cool" image="/mountain_climb.png" />

Avec un peu de chance l’IDE l’a l’importé directement, si c’est pas votre cas il faut l’importer. Si on clique droit dans Visual Studio Code il propose (normalement) de l’importer, si c’est pas le cas il faut préciser :

import { FeatureCard } from '@/components/FeatureCard'

A présent on va simplement dupliquer trois fois notre composant en changeant simplement le titre du composant à chaque fois:

<FeatureCard title="Argument 1" description="This is really cool" image="/mountain_climb.png" />
<FeatureCard title="Argument 2" description="This is really cool" image="/mountain_climb.png" />
<FeatureCard title="Argument 3" description="This is really cool" image="/mountain_climb.png" />

Si on regarde ce que ça donne, on a nos trois colonnes qui commencent à apparaitre même si on peut encore faire mieux!

Si on retourne dans notre composant, on va commencer par structurer un peu plus nos colonnes. On voudrait que ce soit bien toujours des colonnes, donc dans la div qui les contient on va préciser flex flex-col.

Tel que c’est actuellement, les colonnes et leurs images occupent tout l’espace, c’est un peu étouffant. On a besoin d’aérer tout ça.

Pour ça il faudrait définir une largeur au composant. Par contre on va pas la définir directement dans le composant, parce que cette largeur va dépendre des cas d’usage, du contexte. Du coup on va plutôt passer l’information en paramètre au composant.

Une solution assez simple pour arriver à ce qu’on souhaite, c’est de rajouter un champ classNamesur notre composant. Ici on a trois colonnes, donc on veut (disons) que chaque colonne ne fasse qu’un quart de la largeur. Donc on précise className="w-1/4", autrement dit la largeur vaut un quart :

<FeatureCard className="w-1/4" /*...*/ />

On répercute ce meme champ sur les deux autres composants <FeatureCard />.

Par contre ce nouveau champ ne fait pas partie de l’interface tel qu’on l’avait défini dans le composant, donc Visual Studio Code se plaint. On va donc aller gérer ce champ dans le composant.

On va commencer par l’ajouter sur l’interface, on rajoute donc le champ className, qui sera une string. Par contre comme le champ dépend du contexte, on va le mettre en optionnel, et pour ça on met un point d’interrogation après le nom du champ. Donc dans l’interface on a :

interface FeatureCardInterface {
  /* ... */
  className?: string
}

A présent on rajoute ce même classNamedans les paramètres de la fonction de notre composant.

 export const FeatureCard:React.FC<FeatureCardInterface> =  ({title, description, image, className}) => {
/* ...*/
}

Pour finir on fait un peu d’interpolation, pour rajouter le paramètre classNamedans les classes de notre <div>englobante.

Pour ça il y a deux choses à préciser. Ici pour l’attribut classNamede la div, au lieu d’utiliser une chaine de texte avec des guillemets, on va utiliser des accolades et des “backticks”, des guillemets vers l’arrière.

<div className={`flex flex-col`}>

Ensuite on ouvre une interpolation dans le champ texte avec un signe dollar et des accolades, et dedans on met le paramètre className.

<div className={`flex flex-col ${className}`}>

Par contre il faut pas oublier que ce paramètre a été défini dans l’interface comme étant optionnel, donc dans le cas où il est indéfini il faut lui donner une valeur par défaut. Pour ça on va utiliser l’opérateur booléen “ou” avec les deux barres verticales (||) et mettre la chaine vide. Du coup si la variable classNamevaut undefinedalors l’expression className || ''renverra la chaine vide. Et si className est défini l’expression renverra sa valeur.

<div className={`flex flex-col ${className || ''}`}>

Alors on commence à voir la lumière du jour au niveau de la structure. Même si je trouve que l’image prend encore un peu trop de place, donc si je retourne dans le composant <FeatureCard /> on va simplement rajouter un className="p-5"à notre composant <Image/>.

Eventuellement on peut aussi mettre un p-5sur le composant <Image />de la section “Hero”.

Maintenant si on regarde le résultat, tout cela respire beaucoup mieux.

Bon, par contre si on change la taille du navigateur et qu’on se met sur une taille d’écran qui correspond au mobile, on voit qu’il reste encore un peu de travail parce que ça ne s’adapte pas du tout, les colonnes et les images deviennent tout petits.

Typiquement dans ce cas là, en mobile, on met chaque composant en pleine largeur d’écran, et on affiche tout ca en colonne au lieu de l’afficher en ligne.

Du coup si on reprend la <div>qui contient nos trois composant <FeatureCard />. Pour commencer nous allons rajouter un préfixe md:devant la classe justify-around(de qui nous donne donc md:justify-around.

Ensuite, on va ajouter la classe flex-colpuisque par défaut, en mobile, c’est en colonne. Et puis on rajoute l’exception qui correspond aux écrans plus large, où on veut que l’alignement soit en ligne, donc md:flex-row.

Si on teste ce que ça donne, on a bien notre alignement qui se met à jour, par contre les composants sont encore tout petits. Et c’est logique puisqu’on leur avait dit de faire un quart de la largeur, en utilisant la classe w-1/4.

Donc l’idée c’est plutôt de leur dire de prendre toute la largeur par défaut, en utilisant la classe w-full, et de ne prendre le quart de la largeur qu’à partir de la taille d’écran médium, de 768 pixels. Donc on rajoute le préfixe md:devant la classe w-1/4.

<FeatureCard className="w-full md:w-1/4" /*...*/ />

Et on fait la meme chose sur chacun des trois <FeatureCard />. Si on teste le résultat, on voit nos cards qui sont bien en vertical en mobile, en prenant la largeur de l’écran, et qui passent sous la forme d’une ligne bien équilibrée quand on élargit l’écran pour passer en desktop.

Voilà, touche au but. Evidemment, la page est pas encore prête à mettre en ligne, mais ce que manque c’est surtout du contenu intéressant en termes d’image et de texte. Ca dépasse la portée de ce qu’on veut faire ici. En termes de code nos deux sections marchent bien.

Social
Made by kodaps · All rights reserved.
© 2023