The <fieldset>
element is a commonly used HTML element that serves a specific purpose in forms: grouping related information. For example, within a form, you might have sections like “Basic Info,” “Address,” or “Payment Info.”
When working with frameworks like React and UI libraries like MUI, using standard HTML elements directly can sometimes feel limiting. That’s where creating a custom component comes in handy. In this tutorial, we’ll walk through how to create a custom and enhanced Fieldset component using React and MUI.
Let’s dive in and start building this component step by step!
Basic React Fieldset
Let’s start by creating a basic React component named Fieldset
. This component will encapsulate the HTML <fieldset>
element along with its associated <legend>
(the section’s title or label):
const Fieldset = ({ children }) => {
return (
<fieldset>
<legend>title</legend>
{children}
</fieldset>
);
};
The children
prop allows you to pass in any content that should be placed inside the <fieldset>
.
This is a good starting point, but it’s quite limited because the title is fixed and can’t be customized. There’s no way to pass additional properties or customize the component’s behavior.
We can enhance the Fieldset component by introducing props to make it more flexible and reusable:
- Customizing the Legend: We’ll introduce a
title
prop that allows us to dynamically modify the content of the<legend>
element. - Optional Legend: If no title is provided, we can disable the rendering of the
<legend>
element altogether. - Prop Forwarding: To make our component more flexible, we’ll forward any additional props to the
<fieldset>
element.
const Fieldset = ({ title, children, ...props }) => {
return (
<fieldset {...props}>
{title && <legend>{title}</legend>}
{children}
</fieldset>
);
};
The ...props
syntax (known as the rest operator) captures all additional props passed to the Fieldset
component. These props are then spread onto the <fieldset>
element using {...props}
. This allows you to directly pass any other attributes (such as className, style, id, etc.) to the <fieldset>
element from the Fieldset
component.
Now that we’ve created the React component, using it is fun and straightforward! Below is an example of how it can be used within a form:
<form>
<Fieldset title="User Information">
<label>
Username: <input type="text" name="username" />
</label>
<label>
Password: <input type="password" name="password" />
</label>
</Fieldset>
</form>
Advanced MUI Fieldset
When working with MUI, you gain powerful tools for customizing styles. Thanks to the underlying theme system and the sx
approaches for adding styles. Before we dive into styling, let’s first convert our previously created Fieldset
React component into its equivalent MUI-based version:
import React from "react";
import { Box, Typography } from "@mui/material";
const Fieldset = ({ title, children, ...props }) => {
return (
<Box component="fieldset" {...props}>
{title && <Typography component="legend">{title}</Typography>}
{children}
</Box>
);
};
- The Box component is a versatile wrapper that can take on the role of any HTML element via the
component
prop. In this case, we setcomponent="fieldset"
to ensure it renders as a<fieldset>
in the DOM. - The Typography component is used to render the
<legend>
element. MUI’sTypography
component is highly flexible and allows consistent text styling across the application.
Customizing the Component with Styling Props
To make the component more flexible, we can introduce several props that allow for easy customization of common styling properties. These include properties for controlling the border and text styling. Since the legend
title and the fieldset
borders often share similar colors, we can group them for convenience.
Here’s the enhanced version of our component:
import { Box, Typography } from "@mui/material";
const Fieldset = ({
title,
color = "inherit",
titleSize = "1rem",
borderWidth = 1,
borderRadius = 2,
children,
sx = {},
...props
}) => {
return (
<Box
component="fieldset"
sx={{
borderColor: color,
borderWidth: borderWidth,
borderRadius: borderRadius,
...sx,
}}
{...props}
>
{title && (
<Typography
component="legend"
sx={{
color: color,
fontSize: titleSize,
}}
>
{title}
</Typography>
)}
{children}
</Box>
);
};
- The
sx
prop allows you to use MUI’s design tokens and theme system directly within your components. In this example, we usesx
to apply styles based on the props passed to theFieldset
component. - Extending
sx
with the spread operator (...sx
) ensures that any additional styles passed to the component do not override the existing styles. - Using the
color
prop, we simplify the styling process, ensuring that thefieldset
border and thelegend
text share the same color, which is a common requirement.
Let’s look at how to use this enhanced Fieldset
component in a React application:
<Fieldset
title="Basic Information"
color="secondary.main"
titleSize="1.1rem"
borderWidth={2}
borderRadius={1}
>
<label>
Username: <input type="text" name="username" />
</label>
<label>
Password: <input type="password" name="password" />
</label>
</Fieldset>
Advanced Styling with sx
While the exposed props provide a convenient way to style the component, we can further customize using the sx
prop directly. This allows for more advanced and detailed styling:
<Fieldset
title="Basic Information"
titleSize="1.2rem"
borderWidth={2}
borderRadius={3}
color="grey.400"
sx={{
borderStyle: "dashed",
padding: 3,
"& legend": {
backgroundColor: "secondary.main",
color: "grey.200",
padding: "0 8px",
borderRadius: "4px",
},
backgroundColor: "grey.100",
}}
>
{/* Form fields go here */}
</Fieldset>
Adding Icons to the Title
The title is not limited to only text. We can pass any React element to the title
prop. In the example below, we are adding an icon right before the title:
import { Typography, Stack } from "@mui/material";
import InfoIcon from "@mui/icons-material/Info";
<Fieldset
title={
<Stack direction="row" alignItems="center" gap={1}>
<InfoIcon />
Basic Information
</Stack>
}
color="primary.main"
titleSize="1.2rem"
borderWidth={2}
borderRadius={3}
>
{/* Form fields go here */}
</Fieldset>;
Using TypeScript
Making the Fieldset
component TypeScript-friendly brings several benefits, including reducing runtime errors, providing better autocompletion and IntelliSense in editors, and acting as self-documentation for the component.
To convert the previous Fieldset
component to TypeScript, we need to create an interface that defines the types of the props and ensure that the component adheres to that interface:
import React, { ReactNode } from "react";
import { Box, Typography } from "@mui/material";
import { SxProps, Theme } from "@mui/system";
interface FieldsetProps {
title?: ReactNode;
color?: string;
titleSize?: string;
borderWidth?: number;
borderRadius?: number;
children: ReactNode;
sx?: SxProps<Theme>;
}
const Fieldset = ({
title,
color = "grey.800",
titleSize = "1rem",
borderWidth = 1,
borderRadius = 2,
children,
sx = {},
...props
}: FieldsetProps) => {
return (
<Box
component="fieldset"
sx={{
borderColor: color,
borderWidth: borderWidth,
borderRadius: borderRadius,
...sx,
}}
{...props}
>
{title && (
<Typography
component="legend"
sx={{
color: color,
fontSize: titleSize,
}}
>
{title}
</Typography>
)}
{children}
</Box>
);
};
export default Fieldset;
Each prop is defined in the FieldsetProps
interface, and the component uses these types to ensure that the correct type of value is passed. For example, if a developer tries to pass a number to the title prop, TypeScript will throw an error.
Summary & Complete Code
In this tutorial, we created a customizable Fieldset component in React using Material-UI (MUI). Here’s a quick recap:
- Basic Component: Started with a simple React component that encapsulates the
<fieldset>
and<legend>
elements. - MUI Integration: Enhanced the component by using MUI’s
Box
andTypography
components for better styling control. - Styling Flexibility: Introduced props like
borderColor
,borderWidth
,borderRadius
, andcolor
to allow easy customization. - Advanced Styling: Demonstrated the use of the
sx
prop for more advanced, theme-based styling. - TypeScript Conversion: Converted the component to TypeScript, adding type safety and improving development with better autocompletion and documentation.
Here is the complete live code:
Bye for now 👋