Skip to content
Lenso UI

Fieldset

Group related form controls with legends, descriptions, and actions

Usage

import { Fieldset } from '@lenso/ui';
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { FloppyDisk } from "@gravity-ui/icons";import {  Button,  Description,  FieldError,  FieldGroup,  Fieldset,  Form,  Input,  Label,  TextArea,  TextField,} from "@lenso/ui";import { useId, type FormEvent } from "react";import { demoStyles } from "../../demo.stylex";
export function Basic() {  const descriptionId = useId();  function onSubmit(event: FormEvent<HTMLFormElement>) {    event.preventDefault();    alert("Form submitted successfully!");  }  return (    <Form xstyle={demoStyles.wideColumn} onSubmit={onSubmit}>      <Fieldset aria-describedby={descriptionId}>        <Fieldset.Legend>Profile Settings</Fieldset.Legend>        <Description id={descriptionId}>Update your profile information.</Description>        <FieldGroup>          <TextField            name="name"            validate={(value) =>              String(value).length < 3 ? "Name must be at least 3 characters" : null            }          >            <Label required>Name</Label>            <Input required placeholder="John Doe" />            <FieldError />          </TextField>          <TextField name="email">            <Label required>Email</Label>            <Input required type="email" placeholder="[email protected]" />            <FieldError />          </TextField>          <TextField            name="bio"            validate={(value) =>              String(value).length < 10 ? "Bio must be at least 10 characters" : null            }          >            <Label required>Bio</Label>            <TextArea required placeholder="Tell us about yourself..." />            <Description>Minimum 10 characters</Description>            <FieldError />          </TextField>        </FieldGroup>        <Fieldset.Actions>          <Button type="submit">            <FloppyDisk aria-hidden="true" />            Save changes          </Button>          <Button type="reset" variant="secondary">            Cancel          </Button>        </Fieldset.Actions>      </Fieldset>    </Form>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Anatomy

import { Fieldset } from '@lenso/ui';
export default () => (  <Fieldset>    <Fieldset.Legend />    <Fieldset.Group>      {/* form fields go here */}    </Fieldset.Group>    <Fieldset.Actions>      {/* action buttons go here */}    </Fieldset.Actions>  </Fieldset>)

Examples

In Surface

When used inside a Surface component, use variant="secondary" on form controls (Input, TextArea, etc.) to apply the lower emphasis variant suitable for surface backgrounds.

"use client";// Adapted from HeroUI v3.2.6, e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e, Apache-2.0.import { FloppyDisk } from "@gravity-ui/icons";import {  Button,  Description,  FieldError,  Fieldset,  Form,  Input,  Label,  Surface,  TextArea,  TextField,} from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { useId } from "react";
const styles = stylex.create({  backdrop: {    display: "flex",    alignItems: "center",    justifyContent: "center",    borderRadius: 24,    backgroundColor: "var(--surface)",    padding: 24,  },  surface: { width: "100%", minWidth: 380 },  fieldset: { width: "100%" },});
export function OnSurface() {  const descriptionId = useId();  return (    <div {...stylex.props(styles.backdrop)}>      <Surface xstyle={styles.surface}>        <Form          onSubmit={(event) => {            event.preventDefault();            alert("Form submitted successfully!");          }}        >          <Fieldset xstyle={styles.fieldset} aria-describedby={descriptionId}>            <Fieldset.Legend>Profile Settings</Fieldset.Legend>            <Description id={descriptionId}>Update your profile information.</Description>            <Fieldset.Group>              <TextField                name="name"                validate={(value) =>                  String(value).length < 3 ? "Name must be at least 3 characters" : null                }              >                <Label required>Name</Label>                <Input required placeholder="John Doe" variant="secondary" />                <FieldError />              </TextField>              <TextField name="email">                <Label required>Email</Label>                <Input required type="email" placeholder="[email protected]" variant="secondary" />                <FieldError />              </TextField>              <TextField                name="bio"                validate={(value) =>                  String(value).length < 10 ? "Bio must be at least 10 characters" : null                }              >                <Label required>Bio</Label>                <TextArea required placeholder="Tell us about yourself..." variant="secondary" />                <Description>Minimum 10 characters</Description>                <FieldError />              </TextField>            </Fieldset.Group>            <Fieldset.Actions>              <Button type="submit">                <FloppyDisk aria-hidden="true" />                Save changes              </Button>              <Button type="reset" variant="tertiary">                Cancel              </Button>            </Fieldset.Actions>          </Fieldset>        </Form>      </Surface>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Customization

Tailwind CSS

"use client";// Adapted from HeroUI v3.2.6, e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e, Apache-2.0.import { FloppyDisk } from "@gravity-ui/icons";import {  Button,  Description,  FieldError,  FieldGroup,  Fieldset,  Form,  Input,  Label,  TextArea,  TextField,} from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { useId } from "react";
const styles = stylex.create({  form: { width: "100%", maxWidth: 384 },  shell: {    borderRadius: 12,    borderWidth: 1,    borderStyle: "solid",    borderColor: "color-mix(in oklch, var(--border) 70%, transparent)",    backgroundImage: {      default: "linear-gradient(to bottom, oklch(0.985 0 0 / 0.9), white)",      ":is([data-theme='dark'] *)":        "linear-gradient(to bottom, oklch(0.205 0 0 / 0.8), oklch(0.205 0 0))",    },    padding: 16,    boxShadow: {      default: "0 0 0 1px rgb(0 0 0 / 0.05)",      ":is([data-theme='dark'] *)": "0 0 0 1px rgb(255 255 255 / 0.1)",    },  },  legend: {    fontWeight: 500,    color: { default: "oklch(0.269 0 0)", ":is([data-theme='dark'] *)": "oklch(0.97 0 0)" },  },  description: {    color: { default: "oklch(0.439 0 0)", ":is([data-theme='dark'] *)": "oklch(0.708 0 0)" },  },  field: {    borderRadius: 12,    borderWidth: 1,    borderStyle: "solid",    borderColor: "color-mix(in oklch, var(--border) 80%, transparent)",    backgroundColor: "var(--surface)",    boxShadow: {      default: "0 0 0 1px rgb(0 0 0 / 0.05), 0 1px 2px rgb(0 0 0 / 0.05)",      ":focus-visible": "0 0 0 2px oklch(0.708 0 0 / 0.25), 0 1px 2px rgb(0 0 0 / 0.05)",      ":is([data-theme='dark'] *)": {        default: "0 0 0 1px rgb(255 255 255 / 0.1), 0 1px 2px rgb(0 0 0 / 0.05)",        ":focus-visible": "0 0 0 2px oklch(0.556 0 0 / 0.3), 0 1px 2px rgb(0 0 0 / 0.05)",      },    },    transitionProperty: "box-shadow, border-color",    transitionDuration: { default: "150ms", "@media (prefers-reduced-motion: reduce)": "0ms" },  },});
export function CustomStyles() {  const descriptionId = useId();  return (    <Form      xstyle={styles.form}      onSubmit={(event) => {        event.preventDefault();        alert("Form submitted successfully!");      }}    >      <Fieldset xstyle={styles.shell} aria-describedby={descriptionId}>        <Fieldset.Legend xstyle={styles.legend}>Profile Settings</Fieldset.Legend>        <Description id={descriptionId} xstyle={styles.description}>          Update your profile information.        </Description>        <FieldGroup>          <TextField            name="name"            validate={(value) =>              String(value).length < 3 ? "Name must be at least 3 characters" : null            }          >            <Label required>Name</Label>            <Input required xstyle={styles.field} placeholder="John Doe" />            <FieldError />          </TextField>          <TextField name="email">            <Label required>Email</Label>            <Input required type="email" xstyle={styles.field} placeholder="[email protected]" />            <FieldError />          </TextField>          <TextField            name="bio"            validate={(value) =>              String(value).length < 10 ? "Bio must be at least 10 characters" : null            }          >            <Label required>Bio</Label>            <TextArea required xstyle={styles.field} placeholder="Tell us about yourself..." />            <Description>Minimum 10 characters</Description>            <FieldError />          </TextField>        </FieldGroup>        <Fieldset.Actions>          <Button type="submit">            <FloppyDisk aria-hidden="true" />            Save changes          </Button>          <Button type="reset" variant="secondary">            Cancel          </Button>        </Fieldset.Actions>      </Fieldset>    </Form>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Global CSS

Use the @layer components directive to target Fieldset BEM-style classes.

@layer components {  .fieldset {    @apply gap-5 rounded-xl border border-border/60 bg-surface p-6 shadow-field;  }
  .fieldset__legend {    @apply text-lg font-semibold;  }
  .fieldset__field-group {    @apply gap-3 md:grid md:grid-cols-2;  }
  .fieldset__actions {    @apply flex justify-end gap-2 pt-2;  }}

Styling Reference

HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.

CSS Classes

The Fieldset compound component exposes these CSS selectors:

Base Classes [!toc]

  • .fieldset – Root container
  • .fieldset__legend – Legend element
  • .fieldset__field-group – Wrapper for grouped fields
  • .fieldset__actions – Action bar below the fields

API Reference

Fieldset

PropTypeDefaultDescription
classNamestring-Tailwind CSS classes applied to the root element.
childrenReact.ReactNode-Fieldset content (legend, groups, descriptions, actions).
nativePropsReact.HTMLAttributes<HTMLFieldSetElement>Supports native fieldset attributes and events.

Fieldset.Legend

PropTypeDefaultDescription
classNamestring-Tailwind classes for the legend element.
childrenReact.ReactNode-Legend content, usually plain text.
nativePropsReact.HTMLAttributes<HTMLLegendElement>-Native legend attributes.

Fieldset.Group

PropTypeDefaultDescription
classNamestring-Layout and spacing classes for grouped fields.
childrenReact.ReactNode-Form controls to group inside the fieldset.
nativePropsReact.HTMLAttributes<HTMLDivElement>-Native div attributes.

Fieldset.Actions

PropTypeDefaultDescription
classNamestring-Tailwind classes to align action buttons or text.
childrenReact.ReactNode-Action buttons or helper text.
nativePropsReact.HTMLAttributes<HTMLDivElement>-Native div attributes.

See upstream Fieldset showcases. Product showcases are not part of the local component runtime.