SGUI / Development
TextField
Single-line inputs with controlled values, native validation and input-type variations.
Loading examples…
Loading examples…
Examples
Loading examples…
Contains
No fixed SGUI child components.
Component documentation
SGUI / Development
Single-line inputs with controlled values, native validation and input-type variations.
Loading examples…
Loading examples…
Loading examples…
No fixed SGUI child components.
Adjust the public props, then interact with the preview. Source follows the current state.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { TextField } from '@structured-growth/sg-ui/experimental';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
export default function TextFieldPlaygroundExample() {
const [value, setValue] = useState("Design systems");
return (
<>
<ThemeScope density="comfortable">
<Stack gap={3}>
<TextField label={"Course subject"} disabled={false} readOnly={false} description={"Host-supplied course information."} descriptionPlacement="above" errorPlacement="below" required={false} invalid={false} errorMessage="Please review this value." type="text" value={value} onValueChange={setValue} placeholder="Search course subjects" prefix={""} suffix={""} affixAppearance="inline" name="subject" />
</Stack>
</ThemeScope>
</>
);
}
An independent scope demonstrates the inherited spacing and interaction targets.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { TextField } from '@structured-growth/sg-ui/experimental';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
export default function TextFieldCompactExample() {
const [value, setValue] = useState("Design systems");
return (
<>
<ThemeScope density="compact">
<Stack gap={3}>
<TextField label={"Course subject"} disabled={false} readOnly={false} description={"Host-supplied course information."} descriptionPlacement="above" errorPlacement="below" required={false} invalid={false} errorMessage="Please review this value." type="text" value={value} onValueChange={setValue} placeholder="Search course subjects" prefix={""} suffix={""} affixAppearance="inline" name="subject" />
</Stack>
</ThemeScope>
</>
);
}
An independent scope demonstrates the inherited spacing and interaction targets.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { TextField } from '@structured-growth/sg-ui/experimental';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
export default function TextFieldComfortableExample() {
const [value, setValue] = useState("Design systems");
return (
<>
<ThemeScope density="comfortable">
<Stack gap={3}>
<TextField label={"Course subject"} disabled={false} readOnly={false} description={"Host-supplied course information."} descriptionPlacement="above" errorPlacement="below" required={false} invalid={false} errorMessage="Please review this value." type="text" value={value} onValueChange={setValue} placeholder="Search course subjects" prefix={""} suffix={""} affixAppearance="inline" name="subject" />
</Stack>
</ThemeScope>
</>
);
}
Callbacks update local host state. No backend persistence is implied.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { TextField, Button } from '@structured-growth/sg-ui/experimental';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
export default function TextFieldFormExample() {
const [value, setValue] = useState("Design systems");
const [submits, setSubmits] = useState(0);
return (
<>
<ThemeScope density="comfortable">
<form onSubmit={event => { event.preventDefault(); setSubmits(count => count + 1); }}>
<Stack gap={3}>
<Stack gap={3}>
<TextField label={"Course subject"} disabled={false} readOnly={false} description={"Host-supplied course information."} descriptionPlacement="above" errorPlacement="below" required={true} invalid={false} errorMessage="Please review this value." type="text" value={value} onValueChange={setValue} placeholder="Search course subjects" prefix={""} suffix={""} affixAppearance="inline" name="subject" />
</Stack>
<Button type="submit">Save locally</Button>
<Typography role="status">Submitted {submits} times. Connect persistence in your host.</Typography>
</Stack>
</form>
</ThemeScope>
</>
);
}
This field starts empty so its placeholder is visible. The icons are decorative and follow the text direction; they add no tab stops. The visible label remains the accessible name. Selection controls keep their dropdown action; fetching and search results belong to the host.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {TextField} from '@structured-growth/sg-ui/experimental';
import {Box} from '@structured-growth/sg-ui/primitives';
export default function TextFieldcompactnoneExample() {
const [text,setText]=useState("");
return (
<>
<ThemeScope density="compact">
<Box style={{inlineSize:'22rem',maxInlineSize:'100%'}}>
<TextField label="Search courses" placeholder="Search course subjects" type="search" value={text} onValueChange={setText}/>
</Box>
</ThemeScope>
</>
);
}
This field starts empty so its placeholder is visible. The icons are decorative and follow the text direction; they add no tab stops. The visible label remains the accessible name. Selection controls keep their dropdown action; fetching and search results belong to the host.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {TextField} from '@structured-growth/sg-ui/experimental';
import {Box} from '@structured-growth/sg-ui/primitives';
import {SearchIcon} from '@structured-growth/sg-ui/icons';
export default function TextFieldcompactstartExample() {
const [text,setText]=useState("");
return (
<>
<ThemeScope density="compact">
<Box style={{inlineSize:'22rem',maxInlineSize:'100%'}}>
<TextField label="Search courses" placeholder="Search course subjects" startIcon={<SearchIcon size={16}/>} type="search" value={text} onValueChange={setText}/>
</Box>
</ThemeScope>
</>
);
}
This field starts empty so its placeholder is visible. The icons are decorative and follow the text direction; they add no tab stops. The visible label remains the accessible name. Selection controls keep their dropdown action; fetching and search results belong to the host.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {TextField} from '@structured-growth/sg-ui/experimental';
import {Box} from '@structured-growth/sg-ui/primitives';
import {SchoolIcon} from '@structured-growth/sg-ui/icons';
export default function TextFieldcompactendExample() {
const [text,setText]=useState("");
return (
<>
<ThemeScope density="compact">
<Box style={{inlineSize:'22rem',maxInlineSize:'100%'}}>
<TextField label="Search courses" placeholder="Search course subjects" endIcon={<SchoolIcon size={16}/>} type="search" value={text} onValueChange={setText}/>
</Box>
</ThemeScope>
</>
);
}
This field starts empty so its placeholder is visible. The icons are decorative and follow the text direction; they add no tab stops. The visible label remains the accessible name. Selection controls keep their dropdown action; fetching and search results belong to the host.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {TextField} from '@structured-growth/sg-ui/experimental';
import {Box} from '@structured-growth/sg-ui/primitives';
import {SearchIcon,SchoolIcon} from '@structured-growth/sg-ui/icons';
export default function TextFieldcompactbothExample() {
const [text,setText]=useState("");
return (
<>
<ThemeScope density="compact">
<Box style={{inlineSize:'22rem',maxInlineSize:'100%'}}>
<TextField label="Search courses" placeholder="Search course subjects" startIcon={<SearchIcon size={16}/>} endIcon={<SchoolIcon size={16}/>} type="search" value={text} onValueChange={setText}/>
</Box>
</ThemeScope>
</>
);
}
This field starts empty so its placeholder is visible. The icons are decorative and follow the text direction; they add no tab stops. The visible label remains the accessible name. Selection controls keep their dropdown action; fetching and search results belong to the host.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {TextField} from '@structured-growth/sg-ui/experimental';
import {Box} from '@structured-growth/sg-ui/primitives';
export default function TextFieldcomfortablenoneExample() {
const [text,setText]=useState("");
return (
<>
<ThemeScope density="comfortable">
<Box style={{inlineSize:'22rem',maxInlineSize:'100%'}}>
<TextField label="Search courses" placeholder="Search course subjects" type="search" value={text} onValueChange={setText}/>
</Box>
</ThemeScope>
</>
);
}
This field starts empty so its placeholder is visible. The icons are decorative and follow the text direction; they add no tab stops. The visible label remains the accessible name. Selection controls keep their dropdown action; fetching and search results belong to the host.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {TextField} from '@structured-growth/sg-ui/experimental';
import {Box} from '@structured-growth/sg-ui/primitives';
import {SearchIcon} from '@structured-growth/sg-ui/icons';
export default function TextFieldcomfortablestartExample() {
const [text,setText]=useState("");
return (
<>
<ThemeScope density="comfortable">
<Box style={{inlineSize:'22rem',maxInlineSize:'100%'}}>
<TextField label="Search courses" placeholder="Search course subjects" startIcon={<SearchIcon size={16}/>} type="search" value={text} onValueChange={setText}/>
</Box>
</ThemeScope>
</>
);
}
This field starts empty so its placeholder is visible. The icons are decorative and follow the text direction; they add no tab stops. The visible label remains the accessible name. Selection controls keep their dropdown action; fetching and search results belong to the host.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {TextField} from '@structured-growth/sg-ui/experimental';
import {Box} from '@structured-growth/sg-ui/primitives';
import {SchoolIcon} from '@structured-growth/sg-ui/icons';
export default function TextFieldcomfortableendExample() {
const [text,setText]=useState("");
return (
<>
<ThemeScope density="comfortable">
<Box style={{inlineSize:'22rem',maxInlineSize:'100%'}}>
<TextField label="Search courses" placeholder="Search course subjects" endIcon={<SchoolIcon size={16}/>} type="search" value={text} onValueChange={setText}/>
</Box>
</ThemeScope>
</>
);
}
This field starts empty so its placeholder is visible. The icons are decorative and follow the text direction; they add no tab stops. The visible label remains the accessible name. Selection controls keep their dropdown action; fetching and search results belong to the host.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {TextField} from '@structured-growth/sg-ui/experimental';
import {Box} from '@structured-growth/sg-ui/primitives';
import {SearchIcon,SchoolIcon} from '@structured-growth/sg-ui/icons';
export default function TextFieldcomfortablebothExample() {
const [text,setText]=useState("");
return (
<>
<ThemeScope density="comfortable">
<Box style={{inlineSize:'22rem',maxInlineSize:'100%'}}>
<TextField label="Search courses" placeholder="Search course subjects" startIcon={<SearchIcon size={16}/>} endIcon={<SchoolIcon size={16}/>} type="search" value={text} onValueChange={setText}/>
</Box>
</ThemeScope>
</>
);
}
Descriptions and validation messages precede the input. The row aligns fields at their lower edge so inputs line up despite different message heights.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {TextField} from '@structured-growth/sg-ui/experimental';
import {Box,Stack} from '@structured-growth/sg-ui/primitives';
export default function FieldMessagesExample() {
const [values,setValues]=useState(["Design systems","Design systems curriculum","DS-101"]);
return (
<>
<ThemeScope density="compact">
<Stack direction="row" wrap align="end" gap={4}>
<Box style={{inlineSize:'14rem',maxInlineSize:'100%'}}>
<TextField label={"Display name"} description={"Shown in the course list."} descriptionPlacement="above" errorPlacement="above" value={values[0]} onValueChange={value=>setValues(current=>current.map((item,index)=>index===0?value:item))}/>
</Box>
<Box style={{inlineSize:'14rem',maxInlineSize:'100%'}}>
<TextField label={"Internal name"} description={"Use a name people recognize when browsing courses and reports."} descriptionPlacement="above" errorPlacement="above" value={values[1]} onValueChange={value=>setValues(current=>current.map((item,index)=>index===1?value:item))}/>
</Box>
<Box style={{inlineSize:'14rem',maxInlineSize:'100%'}}>
<TextField label={"Reference code"} description={"A code for this course."} descriptionPlacement="above" errorPlacement="above" invalid errorMessage="Use a unique reference code." value={values[2]} onValueChange={value=>setValues(current=>current.map((item,index)=>index===2?value:item))}/>
</Box>
</Stack>
</ThemeScope>
</>
);
}
Descriptions and validation messages follow the input. The row aligns fields at their upper edge.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {TextField} from '@structured-growth/sg-ui/experimental';
import {Box,Stack} from '@structured-growth/sg-ui/primitives';
export default function FieldMessagesExample() {
const [values,setValues]=useState(["Design systems","Design systems curriculum","DS-101"]);
return (
<>
<ThemeScope density="compact">
<Stack direction="row" wrap align="start" gap={4}>
<Box style={{inlineSize:'14rem',maxInlineSize:'100%'}}>
<TextField label={"Display name"} description={"Shown in the course list."} descriptionPlacement="below" errorPlacement="below" value={values[0]} onValueChange={value=>setValues(current=>current.map((item,index)=>index===0?value:item))}/>
</Box>
<Box style={{inlineSize:'14rem',maxInlineSize:'100%'}}>
<TextField label={"Internal name"} description={"Use a name people recognize when browsing courses and reports."} descriptionPlacement="below" errorPlacement="below" value={values[1]} onValueChange={value=>setValues(current=>current.map((item,index)=>index===1?value:item))}/>
</Box>
<Box style={{inlineSize:'14rem',maxInlineSize:'100%'}}>
<TextField label={"Reference code"} description={"A code for this course."} descriptionPlacement="below" errorPlacement="below" invalid errorMessage="Use a unique reference code." value={values[2]} onValueChange={value=>setValues(current=>current.map((item,index)=>index===2?value:item))}/>
</Box>
</Stack>
</ThemeScope>
</>
);
}
Descriptions and validation messages precede the input. The row aligns fields at their lower edge so inputs line up despite different message heights.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {TextField} from '@structured-growth/sg-ui/experimental';
import {Box,Stack} from '@structured-growth/sg-ui/primitives';
export default function FieldMessagesExample() {
const [values,setValues]=useState(["Design systems","Design systems curriculum","DS-101"]);
return (
<>
<ThemeScope density="comfortable">
<Stack direction="row" wrap align="end" gap={4}>
<Box style={{inlineSize:'14rem',maxInlineSize:'100%'}}>
<TextField label={"Display name"} description={"Shown in the course list."} descriptionPlacement="above" errorPlacement="above" value={values[0]} onValueChange={value=>setValues(current=>current.map((item,index)=>index===0?value:item))}/>
</Box>
<Box style={{inlineSize:'14rem',maxInlineSize:'100%'}}>
<TextField label={"Internal name"} description={"Use a name people recognize when browsing courses and reports."} descriptionPlacement="above" errorPlacement="above" value={values[1]} onValueChange={value=>setValues(current=>current.map((item,index)=>index===1?value:item))}/>
</Box>
<Box style={{inlineSize:'14rem',maxInlineSize:'100%'}}>
<TextField label={"Reference code"} description={"A code for this course."} descriptionPlacement="above" errorPlacement="above" invalid errorMessage="Use a unique reference code." value={values[2]} onValueChange={value=>setValues(current=>current.map((item,index)=>index===2?value:item))}/>
</Box>
</Stack>
</ThemeScope>
</>
);
}
Descriptions and validation messages follow the input. The row aligns fields at their upper edge.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {TextField} from '@structured-growth/sg-ui/experimental';
import {Box,Stack} from '@structured-growth/sg-ui/primitives';
export default function FieldMessagesExample() {
const [values,setValues]=useState(["Design systems","Design systems curriculum","DS-101"]);
return (
<>
<ThemeScope density="comfortable">
<Stack direction="row" wrap align="start" gap={4}>
<Box style={{inlineSize:'14rem',maxInlineSize:'100%'}}>
<TextField label={"Display name"} description={"Shown in the course list."} descriptionPlacement="below" errorPlacement="below" value={values[0]} onValueChange={value=>setValues(current=>current.map((item,index)=>index===0?value:item))}/>
</Box>
<Box style={{inlineSize:'14rem',maxInlineSize:'100%'}}>
<TextField label={"Internal name"} description={"Use a name people recognize when browsing courses and reports."} descriptionPlacement="below" errorPlacement="below" value={values[1]} onValueChange={value=>setValues(current=>current.map((item,index)=>index===1?value:item))}/>
</Box>
<Box style={{inlineSize:'14rem',maxInlineSize:'100%'}}>
<TextField label={"Reference code"} description={"A code for this course."} descriptionPlacement="below" errorPlacement="below" invalid errorMessage="Use a unique reference code." value={values[2]} onValueChange={value=>setValues(current=>current.map((item,index)=>index===2?value:item))}/>
</Box>
</Stack>
</ThemeScope>
</>
);
}
'use client';
import { useState } from 'react';
import {TextField} from '@structured-growth/sg-ui/primitives';
export default function AffixedFieldExample() {
const [value,setValue]=useState("25");
return (
<>
<TextField label={"Amount in whole US dollars"} description={"Enter whole dollars. The fixed symbols are not part of the editable value."} prefix={"$"} suffix={".00"} affixAppearance="inline" density="compact" value={value} onValueChange={setValue} inputMode="numeric" pattern="[0-9]+" disabled={false} readOnly={false} invalid={false}/>
</>
);
}
'use client';
import { useState } from 'react';
import {TextField} from '@structured-growth/sg-ui/primitives';
export default function AffixedFieldExample() {
const [value,setValue]=useState("25");
return (
<>
<TextField label={"Amount in whole US dollars"} description={"Enter whole dollars. The fixed symbols are not part of the editable value."} prefix={"$"} suffix={".00"} affixAppearance="inline" density="comfortable" value={value} onValueChange={setValue} inputMode="numeric" pattern="[0-9]+" disabled={false} readOnly={false} invalid={false}/>
</>
);
}
'use client';
import { useState } from 'react';
import {TextField} from '@structured-growth/sg-ui/primitives';
export default function AffixedFieldExample() {
const [value,setValue]=useState("25");
return (
<>
<TextField label={"Amount in whole US dollars"} description={"Enter whole dollars. The fixed symbols are not part of the editable value."} prefix={"$"} suffix={".00"} affixAppearance="subtle" density="compact" value={value} onValueChange={setValue} inputMode="numeric" pattern="[0-9]+" disabled={false} readOnly={false} invalid={false}/>
</>
);
}
'use client';
import { useState } from 'react';
import {TextField} from '@structured-growth/sg-ui/primitives';
export default function AffixedFieldExample() {
const [value,setValue]=useState("25");
return (
<>
<TextField label={"Amount in whole US dollars"} description={"Enter whole dollars. The fixed symbols are not part of the editable value."} prefix={"$"} suffix={".00"} affixAppearance="subtle" density="comfortable" value={value} onValueChange={setValue} inputMode="numeric" pattern="[0-9]+" disabled={false} readOnly={false} invalid={false}/>
</>
);
}
'use client';
import { useState } from 'react';
import {TextField} from '@structured-growth/sg-ui/primitives';
export default function AffixedFieldExample() {
const [value,setValue]=useState("12.5");
return (
<>
<TextField label={"Weight in kilograms"} description={"Enter the measurement without the unit."} prefix={""} suffix={"kg"} affixAppearance="inline" density="comfortable" value={value} onValueChange={setValue} inputMode="decimal" disabled={false} readOnly={false} invalid={false}/>
</>
);
}
'use client';
import { useState } from 'react';
import {TextField} from '@structured-growth/sg-ui/primitives';
export default function AffixedFieldExample() {
const [value,setValue]=useState("courses");
return (
<>
<TextField label={"Workspace subdomain on example.com"} description={"Enter only the workspace name. The host builds and validates the complete URL."} prefix={"https://"} suffix={".example.com"} affixAppearance="subtle" density="comfortable" value={value} onValueChange={setValue} disabled={false} readOnly={false} invalid={false}/>
</>
);
}
'use client';
import { useState } from 'react';
import {TextField} from '@structured-growth/sg-ui/primitives';
import {SchoolIcon} from '@structured-growth/sg-ui/icons';
export default function AffixedFieldExample() {
const [value,setValue]=useState("SG101");
return (
<>
<TextField label={"Course code"} description={"A non-interactive icon can occupy the prefix slot, beside fixed text at the end."} prefix={<SchoolIcon size={16}/>} suffix={"catalog"} affixAppearance="inline" density="comfortable" value={value} onValueChange={setValue} disabled={false} readOnly={false} invalid={false}/>
</>
);
}
'use client';
import { useState } from 'react';
import {TextField} from '@structured-growth/sg-ui/primitives';
import {SearchIcon,SchoolIcon} from '@structured-growth/sg-ui/icons';
export default function AffixedFieldExample() {
const [value,setValue]=useState("101");
return (
<>
<TextField label={"Course code in the public catalog"} description={"Icon slots and text affixes can coexist. Keep units and meaning in the label or guidance."} prefix={"SG-"} suffix={"public"} affixAppearance="subtle" density="comfortable" value={value} onValueChange={setValue} disabled={false} readOnly={false} invalid={false} startIcon={<SearchIcon size={16}/>} endIcon={<SchoolIcon size={16}/>}/>
</>
);
}
'use client';
import { useState } from 'react';
import {TextField} from '@structured-growth/sg-ui/primitives';
export default function AffixedFieldExample() {
const [value,setValue]=useState("25");
return (
<>
<TextField label={"Amount in whole US dollars"} description={"Enter whole dollars. The fixed symbols are not part of the editable value."} prefix={"$"} suffix={".00"} affixAppearance="subtle" density="comfortable" value={value} onValueChange={setValue} inputMode="numeric" pattern="[0-9]+" disabled={false} readOnly={true} invalid={false}/>
</>
);
}
'use client';
import { useState } from 'react';
import {TextField} from '@structured-growth/sg-ui/primitives';
export default function AffixedFieldExample() {
const [value,setValue]=useState("25");
return (
<>
<TextField label={"Amount in whole US dollars"} description={"Enter whole dollars. The fixed symbols are not part of the editable value."} prefix={"$"} suffix={".00"} affixAppearance="subtle" density="comfortable" value={value} onValueChange={setValue} inputMode="numeric" pattern="[0-9]+" disabled={true} readOnly={false} invalid={false}/>
</>
);
}
'use client';
import { useState } from 'react';
import {TextField} from '@structured-growth/sg-ui/primitives';
export default function AffixedFieldExample() {
const [value,setValue]=useState("25");
return (
<>
<TextField label={"Amount in whole US dollars"} description={"Enter whole dollars. The fixed symbols are not part of the editable value."} prefix={"$"} suffix={".00"} affixAppearance="inline" density="comfortable" value={value} onValueChange={setValue} inputMode="numeric" pattern="[0-9]+" disabled={false} readOnly={false} invalid={true} errorMessage="Review the amount before continuing."/>
</>
);
}
Prefix and suffix follow the logical start and end. The host supplies localized labels and currency conventions.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {TextField} from '@structured-growth/sg-ui/primitives';
export default function RtlAffixesExample() {
const [value,setValue]=useState("25");
return (
<>
<ThemeScope dir="rtl"><TextField label="المبلغ بالدولار الأمريكي" description="أدخل مبلغًا بالدولارات الكاملة" prefix="$" suffix=".00" affixAppearance="subtle" inputMode="numeric" value={value} onValueChange={setValue}/></ThemeScope>
</>
);
}
The native pattern validates whole-dollar digits. Input mode suggests a numeric keyboard; it does not enforce validation. Reviewing creates a local draft, without persistence.
'use client';
import { useState } from 'react';
import {TextField,Stack,Button,Typography} from '@structured-growth/sg-ui/primitives';
export default function WholeDollarFormExample() {
const [result,setResult]=useState("");
return (
<>
<form onSubmit={event=>{event.preventDefault();const data=new FormData(event.currentTarget);setResult(`Draft amount: USD ${data.get("amount")}.00`);}} onReset={()=>setResult("")}><Stack gap={3}><TextField label="Amount in whole US dollars" description="Enter whole dollars using digits only. Cents are fixed at zero." name="amount" prefix="$" suffix=".00" affixAppearance="subtle" inputMode="numeric" pattern="[0-9]+" required defaultValue="25" errorMessage="Enter a whole-dollar amount using digits only."/><Stack direction="row" gap={2}><Button type="submit">Review amount</Button><Button type="reset" variant="outlined">Reset amount</Button></Stack>{result&&<Typography role="status">{result}</Typography>}</Stack></form>
</>
);
}
Native reset restores the uncontrolled email default. The host explicitly restores the controlled name and clears its submission result. Required and email validation run before submission; no account or network request is made.
'use client';
import { useState } from 'react';
import { Button, TextField } from '@structured-growth/sg-ui/experimental';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
export default function RegistrationFormExample() {
const [name,setName]=useState("Alex Learner");
const [result,setResult]=useState("");
return (
<>
<form aria-label="Course registration" onReset={() => { setName('Alex Learner'); setResult(''); }} onSubmit={event => {
event.preventDefault();
const data=new FormData(event.currentTarget);
setResult('Registration prepared for '+data.get('name')+' ('+data.get('email')+'). Host owns persistence.');
}}>
<Stack gap={3}>
<TextField label="Learner name" name="name" required value={name} onValueChange={setName} />
<TextField label="Contact email" name="email" type="email" required defaultValue="alex@example.com" />
<Stack direction="row" wrap gap={2}><Button type="submit">Prepare registration</Button><Button type="reset" variant="outlined">Reset registration</Button></Stack>
<Typography role="status">{result}</Typography>
</Stack>
</form>
</>
);
}
Native fieldset and legend name the group. Each field has its own label and native validation error, plus shared guidance. Reset restores the uncontrolled family name and the host explicitly restores the controlled given name. Submission prepares a local result; the host owns persistence.
import {useId, useState} from 'react';
import {Button, Stack, TextField, Typography} from '@structured-growth/sg-ui/primitives';
export function ContactForm() {
const id = useId();
const [given, setGiven] = useState("Alex");
const [result, setResult] = useState("");
return <form onReset={() => {setGiven('Alex'); setResult('');}} onSubmit={event => {
event.preventDefault();
const data = new FormData(event.currentTarget);
setResult('Contact prepared for ' + data.get('given') + ' ' + data.get('family') + '. Host owns persistence.');
}}>
<fieldset aria-describedby={id + '-help'}>
<legend>Course contact</legend>
<Typography id={id + '-help'}>Supply both names for the course contact.</Typography>
<Stack gap={3}>
<TextField label="Given name" name="given" autoComplete="given-name" required value={given} onValueChange={setGiven}
aria-describedby={id + '-help'} errorMessage="Enter a given name." />
<TextField label="Family name" name="family" autoComplete="family-name" required defaultValue="Learner"
aria-describedby={id + '-help'} errorMessage="Enter a family name." />
<Stack direction="row" gap={2} wrap><Button type="submit">Prepare contact</Button><Button type="reset" variant="outlined">Reset contact</Button></Stack>
<Typography role="status">{result}</Typography>
</Stack>
</fieldset>
</form>;
}