:has():valid:invalid:placeholder-shown@starting-styleinterpolate-size
Cette démonstration utilise la pseudo-classe fonctionnelle :has() qui va nous permettre de gérer les états de validité ou d'erreur des champs renseignés et d'adapter les actions à effectuer en conséquence.
<form>
<div class=cadre-input>
<label for="email">Email :</label>
<div class=input-form>
<svg></svg>
<input required type="email" id="email" title="Veuillez entrer une adresse valide" placeholder="Veuillez entrer une adresse valide">
</div>
<div class="erreur"><span>Veuillez entrer une adresse valide</span></div>
</div>
<div class=cadre-input>
<label for="password">Mot de passe :</label>
<div class=input-form>
<svg>...</svg>
<input required type="password" id="password" placeholder="Votre mot de passe" title="Votre mot de passe doit contenir au moins 8 caractères" pattern=".{8,}">
</div>
<div class="erreur"><span>Votre mot de passe doit contenir au moins 8 caractères</span></div>
</div>
<button type="submit">Se connecter</button>
</form>
*{box-sizing: border-box}
:root {
interpolate-size: allow-keywords;
--texte: oklch(52% 0 0);
--rouge: oklch(48% .2 30);
--vert: oklch(58% 0.18 145);
--jaune: color-mix(in oklch,var(--vert),var(--blanc));
--gris: oklch(80% 0 0);
--blanc:oklch(1 0 0);
}
form:is(:focus,:focus-within){border-color: oklch(55% 0 0);}
form{
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04),
0 4px 3px rgba(0, 0, 0, 0.03),
0 8px 6px rgba(0, 0, 0, 0.03),
0 14px 11px rgba(0, 0, 0, 0.02),
0 22px 17px rgba(0, 0, 0, 0.01),
0 32px 24px rgba(0, 0, 0, 0.01);
border-radius:.5lh;
border:1px solid var(--gris);
display: grid;
grid-template-columns:var(--grid);
max-width: 75ch;
margin:auto;
row-gap: 2rem;
column-gap: 1rem;
padding:1rem;
}
.cadre-input {
grid-column: span 2;
display: grid;
gap: 1rem;
grid-template-columns: var(--sub);
grid-template-rows: auto minmax(3rem,1fr);
align-items: center;
}
form label {
display: flex;
font-weight: bold;
color:var(--couleur-form);
transition: color var(--transition);
}
form input {
padding:0;
background:none;
font-family: inherit;
width:100%;
transition: border-color var(--transition);
border: 2px solid var(--couleur-form);
border-radius: 4px;
height: 3rem;
padding-left:4cap;
font-weight: 400;
font-size:1rem;
}
form input::placeholder {color:transparent;}
[type="submit"] {
font-family: inherit;
border:none;
place-self:center;
grid-column:span 2;
padding: 1lh;
border-radius: .25lh;
pointer-events: none;
background:linear-gradient(var(--vert) 0 0) no-repeat 0 0/var(--progression,0) 2px,#ececec;
transition:background-size var(--transition) var(--transition);
font-weight: bold;
font-size: 1rem;
color: var(--couleur-form);
}
form input:is(:focus-within,:focus-visible){outline:2px solid var(--couleur-form);outline-offset: 2px}
.input-form{display: grid;}
.input-form>*{grid-area:1/1}
.input-form svg{
border-radius: 4px;
margin-left:.5rem;
width:2.5cap;
height:2.5cap;
fill:var(--couleur-form);
align-self:center
}
.cadre-input:has(:user-invalid) {
--couleur-form: var(--rouge);
}
.cadre-input:has(:focus) {
--couleur-form: var(--jaune);
}
.cadre-input:has(:valid) {
--couleur-form: var(--vert);
}
.cadre-input:has(:placeholder-shown) {
--couleur-form: var(--texte);
}
.erreur {
display: none;
translate:0 10px;
opacity:0;
white-space: nowrap;
font-size: .875rem;
grid-column:var(--gc);
}
.erreur span {
position: relative;
background-color: var(--rouge);
border-radius: 5px;
padding: 1rlh;
display:block;
white-space: pre-wrap;
width:fit-content;
color:var(--blanc);
text-align: center;
}
.erreur span::after {
content: "";
position: absolute;
top: -5px;
left:10px;
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-bottom: 5px solid var(--rouge);
}
.cadre-input:first-of-type:has(:valid) ~ [type="submit"]{ --progression:50%;}
.cadre-input:has(:valid) ~ .cadre-input:last-of-type:has(:valid) ~ [type="submit"] {
--progression:100%;
background-color:var(--vert);
--color:var(--blanc);
transition:background-size var(--transition), background-color var(--transition) calc(var(--transition)*2),color var(--transition) calc(var(--transition)*2);
}
.cadre-input:has(:invalid:not(:focus):not(:placeholder-shown)) .erreur {
display: block;
translate:0 0;
opacity:1;
}
.mail:not(:has(:valid)) + .mdp {
height: 0;
opacity: 0;
overflow: clip;
}
@media (min-width:40rem){:root{--grid: auto minmax(30ch,1fr);--gc:2}
.cadre-input{--sub:subgrid}}
@media (prefers-reduced-motion: no-preference) {
:root{--transition: .25s;}
.cadre-input:has(:invalid:not(:focus):not(:placeholder-shown)) .erreur {
transition:all var(--transition),
translate var(--transition),
display var(--transition);
}
.mdp {
transition: var(--transition),opacity var(--transition);
}
@starting-style {.cadre-input:has(:invalid:not(:focus):not(:placeholder-shown)) .erreur{
opacity:0;
translate:0 10px;
}
}
}