Skip to content

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.

  • 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.

  1. Pick one screen and list what it uses. Note every import from @packages/ui/* and @chakra-ui/react.

  2. 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.

  3. Replace the imports and props. See the table and the example below.

  4. Replace style props with layout components. Chakra’s p, mt, w and gap props do not exist here. Use Stack, Flex and Grid with gap, and style or a class for a one-off size.

  5. Run the screen’s tests. Queries by role and label keep working; queries by Chakra class names or structure need updating. See Jest setup.

  6. Compare with Figma in light and dark, and check keyboard use: Tab order, Enter and Escape.

  7. Remove what is no longer used. When the last screen that used a packages/ui component has moved, delete the component.

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

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.

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:

  • Button defaults to type="button". A submit button needs type="submit".
  • loading disables the button and shows the spinner; there is no separate isLoading.
  • The auth card is wider and roomier than the largest Card padding, so it sets its size with style. That is a one-off, which is what style is for.
  • Checkbox reports changes through onCheckedChange, not onChange.

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.