Migrating a screen from Chakra
The frontend is migrated one screen at a time. packages/ui is frozen, and both libraries run
side by side until the last screen has moved. The login screen was the first; the examples
here come from it.
Why side by side works
Section titled “Why side by side works”- Sulmu UI classes and variables are prefixed with
su, and its stylesheet has no reset, so it does not change how Chakra components look. - Chakra’s styles do not target
su-classes. - Both are shared singletons in Module Federation, so adding the library does not duplicate anything.
A single screen should not mix the two, though. Finish a screen before starting the next.
-
Pick one screen and list what it uses. Note every import from
@packages/ui/*and@chakra-ui/react. -
Check each one against the library. Find the Sulmu UI component on this site. If one is missing and it is generic, add it to the library first; do not build it in the app.
-
Replace the imports and props. See the table and the example below.
-
Replace style props with layout components. Chakra’s
p,mt,wandgapprops do not exist here. UseStack,FlexandGridwithgap, andstyleor a class for a one-off size. -
Run the screen’s tests. Queries by role and label keep working; queries by Chakra class names or structure need updating. See Jest setup.
-
Compare with Figma in light and dark, and check keyboard use: Tab order, Enter and Escape.
-
Remove what is no longer used. When the last screen that used a
packages/uicomponent has moved, delete the component.
What changes
Section titled “What changes”| Before | After |
|---|---|
Input from @packages/ui/atoms |
Input |
Field from @packages/ui/molecules |
Field |
id on the input and labelProps={{ htmlFor }} on the field |
ids={{ control: 'email' }} on the Field |
Box, Flex, HStack |
Flex |
VStack, Stack |
Stack |
SimpleGrid, Grid |
Grid with columns or minChildWidth |
Text, Heading |
Text with textStyle, and as for the heading level |
Style props (p={4}, mt={2}, color="gray.600") |
gap on a layout component, tone on Text, or style |
| Link styled as a button | <Button asChild> around the link |
| Chakra theme tokens | --su-* CSS variables |
Example: the email field
Section titled “Example: the email field”Before, the label was tied to the input by hand:
import { Input } from '@packages/ui/atoms'import { Field } from '@packages/ui/molecules'
<Field required label="Email" invalid={touched.email && !!errors.email} errorText={getFormFieldError(touched, errors, 'email')} labelProps={{ htmlFor: 'email' }}> <Input id="email" name="email" value={values.email} onChange={handleChange} /></Field>After, the Field owns the id, so the label, the input and the error text all point at the
same element:
import { Field, Input } from '@beyul/sulmu-ui'
<Field required ids={{ control: 'email' }} label="Email" invalid={Boolean(touched.email && errors.email)} errorText={getFormFieldError(touched, errors, 'email')}> <Input name="email" value={values.email} onChange={handleChange} /></Field>Do not put id on the Input. The Field generates the id it uses for the label and for
aria-describedby; an id set on the input alone leaves the label pointing at nothing.
Example: the form layout
Section titled “Example: the form layout”import { Button, Card, Checkbox, Divider, Flex, Stack, Text } from '@beyul/sulmu-ui'
<Card padding="none" elevated style={{ width: 446, maxWidth: '100%', padding: 44 }}> <Stack gap={4}> <Flex justify="between" align="baseline" gap={2}> <Text as="h1" textStyle="heading4">Log In</Text> <Text as="span" textStyle="labelS" tone="subtle">{hostname}</Text> </Flex> <Divider /> <EmailInput /> <PasswordInput /> <Checkbox checked={rememberMe} onCheckedChange={toggleRememberMe}>Remember me</Checkbox> <Button type="submit" loading={isSubmitting} fullWidth>Log In</Button> </Stack></Card>Things to notice:
Buttondefaults totype="button". A submit button needstype="submit".loadingdisables the button and shows the spinner; there is no separateisLoading.- The auth card is wider and roomier than the largest
Cardpadding, so it sets its size withstyle. That is a one-off, which is whatstyleis for. Checkboxreports changes throughonCheckedChange, notonChange.
Not migrated yet
Section titled “Not migrated yet”On the login screen, the shared auth layout (AuthLayout, AuthHero) and the toasts still
use Chakra. Toasts will move when the app’s toaster is replaced with the library’s
Toast.