react
react copied to clipboard
FormControl.Label causing Layout Shift
Describe the bug
FormControl.Label
is causing Layout Shift on page load (see gif below)
To Reproduce
- Create a page using
FormControl
with aFormControl.Label
component. For example:
<FormControl>
<FormControl.Label>Username</FormControl.Label>
<TextInput />
</FormControl>
- Refresh the page.
Expected behavior
<FormControl.Label>
should not flash on page load.
Screenshots
Gif of the page being refreshed
Static image showing the exact frame where <FormControl.Label>
is invisible
Desktop (please complete the following information):
- OS: macOS
- Browser tested on Chrome (latest) and Safari (latest)
Additional context
There's no logic applied to the <FormControl.Label>
component. Here's the code from the gif above:
return (
<form style={{ width: '100%' }} onSubmit={handleSubmit}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
{globalErrorMessage && <Flash variant="danger">{globalErrorMessage}</Flash>}
<FormControl id="username">
<FormControl.Label>Nome de usuário</FormControl.Label>
<TextInput
ref={usernameRef}
onChange={clearErrors}
name="username"
size="large"
autoCorrect="off"
autoCapitalize="off"
spellCheck={false}
aria-label="Seu nome de usuário"
/>
{errorObject?.key === 'username' && (
<FormControl.Validation variant="error">{errorObject.message}</FormControl.Validation>
)}
{errorObject?.type === 'string.alphanum' && (
<FormControl.Caption>Dica: use somente letras e números, por exemplo: nomeSobrenome4 </FormControl.Caption>
)}
</FormControl>
<FormControl id="email">
<FormControl.Label>Email</FormControl.Label>
<TextInput
ref={emailRef}
onChange={clearErrors}
name="email"
size="large"
autoCorrect="off"
autoCapitalize="off"
spellCheck={false}
aria-label="Seu email"
/>
{errorObject?.key === 'email' && (
<FormControl.Validation variant="error">{errorObject.message}</FormControl.Validation>
)}
</FormControl>
<FormControl id="password">
<FormControl.Label>Senha</FormControl.Label>
<TextInput
ref={passwordRef}
onChange={clearErrors}
name="password"
type="password"
autoCorrect="off"
autoCapitalize="off"
spellCheck={false}
size="large"
aria-label="Sua senha"
/>
{errorObject?.key === 'password' && (
<FormControl.Validation variant="error">{errorObject.message}</FormControl.Validation>
)}
</FormControl>
<FormControl>
<FormControl.Label visuallyHidden>Criar cadastro</FormControl.Label>
<Button variant="primary" size="large" type="submit" disabled={isLoading} aria-label="Criar cadastro">
Criar cadastro
</Button>
</FormControl>
</Box>
</form>
);
Additional context (bonus)
I'm in love with Primer. It's been a long time since I've been this happy developing interfaces.
To everyone involved in this project: you are amazing 🤝
I'm also having this issue. simple reproduction CodeSandbox demo
The issue occurs because the FormControl
is using double-render strategy.
https://github.com/primer/react/blob/03f3427ae18c70a143a43545f31bcbcba03ef2e4/src/utils/create-slots.tsx#L25-L30
Only input component was rendered at the first render.
1st render | 2nd render | |
---|---|---|
Slots | ![]() |
![]() |
DOM | ![]() |
![]() |
It might be hard to prevent Layout Shift but I hope at least all components can start render since the same render, instead of first render the input then render label or other stuff.
I guess these solutions might work
- Wrapping the input component with slot as well, contents would be rendered since the 2nd render. https://github.com/primer/react/blob/03f3427ae18c70a143a43545f31bcbcba03ef2e4/src/FormControl/FormControl.tsx#L133-L138
- In SlotsContext, manage slots with
useState
instead ofuseRef
. React would re-render immediately if its state changed within effect. demo This way could potentially prevent layout shift.
Thanks to every contributors of this project, it is amazing!
Hi!
First of all, sorry @filipedeschamps, we missed your initial message!
Thanks for reporting this. @EnixCoda is right, this is related to double rendered strategy for slots, we are tracking it here: https://github.com/primer/react/issues/1690
I think this is likely solved by https://github.com/primer/react/pull/2216; now both renders should happen before the first paint so there should be no visible flash. I don't see the problem on my end anymore. Please let us know if you do still see it!