SGUI / Development
AppModal
Dialog sizes, dismissal options and host-owned forms.
Loading examples…
Loading examples…
Examples
Loading examples…
Component documentation
SGUI / Development
Dialog sizes, dismissal options and host-owned forms.
Loading examples…
Loading examples…
Loading examples…
No changes saved
Adjust dialog props and copy a complete example with host-owned draft and actions.
No changes saved
'use client';
import { useState } from 'react';
import { AppModal } from '@structured-growth/sg-ui/components/AppModal';
import { Button } from '@structured-growth/sg-ui/components/Button';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
import { TextField } from '@structured-growth/sg-ui/experimental';
export default function DialogExample() {
const [open, setOpen] = useState(false);
const [name, setName] = useState("Course basics");
const [saved, setSaved] = useState("No changes saved");
return (
<>
<Stack gap={3}>
<Button onPress={() => setOpen(true)}>Open example dialog</Button>
<Typography role="status">{saved}</Typography>
<AppModal open={open} title="Course settings" size="md" showCloseButton
disableBackdropClose={false} disableEscapeKeyDown={false} onClose={() => setOpen(false)}
primaryAction={{label:'Save settings', onPress:() => {setSaved(`Saved ${name}`); setOpen(false);}}}
secondaryAction={{label:'Cancel', onPress:() => setOpen(false)}}>
<Stack gap={3}>
<Typography>This example keeps its draft and save action in the docs app.</Typography>
<TextField label="Course name" value={name} onValueChange={setName} />
</Stack>
</AppModal>
</Stack>
</>
);
}
The xs width adapts to the available viewport.
'use client';
import { useState } from 'react';
import { AppModal } from '@structured-growth/sg-ui/components/AppModal';
import { Button } from '@structured-growth/sg-ui/components/Button';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
export default function ModalXSExample() {
const [open, setOpen] = useState(false);
return (
<>
<Stack gap={3}>
<Button variant="outlined" onPress={() => setOpen(true)}>Open xs dialog</Button>
<AppModal open={open} title="Course overview" subtitle="Select a size appropriate to the task." size={"xs"} showCloseButton onClose={() => setOpen(false)} primaryAction={{label:"Done",onPress:() => setOpen(false)}}>
<Stack gap={3}><Typography variant="h5">Leadership foundations</Typography><Typography>Six modules with practical exercises and instructor feedback.</Typography><Typography tone="muted">This read-only summary has one action and no step indicator.</Typography></Stack>
</AppModal>
</Stack>
</>
);
}
The sm width adapts to the available viewport.
'use client';
import { useState } from 'react';
import { AppModal } from '@structured-growth/sg-ui/components/AppModal';
import { Button } from '@structured-growth/sg-ui/components/Button';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
export default function ModalSMExample() {
const [open, setOpen] = useState(false);
return (
<>
<Stack gap={3}>
<Button variant="outlined" onPress={() => setOpen(true)}>Open sm dialog</Button>
<AppModal open={open} title="Course overview" subtitle="Select a size appropriate to the task." size={"sm"} showCloseButton onClose={() => setOpen(false)} primaryAction={{label:"Done",onPress:() => setOpen(false)}}>
<Stack gap={3}><Typography variant="h5">Leadership foundations</Typography><Typography>Six modules with practical exercises and instructor feedback.</Typography><Typography tone="muted">This read-only summary has one action and no step indicator.</Typography></Stack>
</AppModal>
</Stack>
</>
);
}
The md width adapts to the available viewport.
'use client';
import { useState } from 'react';
import { AppModal } from '@structured-growth/sg-ui/components/AppModal';
import { Button } from '@structured-growth/sg-ui/components/Button';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
export default function ModalMDExample() {
const [open, setOpen] = useState(false);
return (
<>
<Stack gap={3}>
<Button variant="outlined" onPress={() => setOpen(true)}>Open md dialog</Button>
<AppModal open={open} title="Course overview" subtitle="Select a size appropriate to the task." size={"md"} showCloseButton onClose={() => setOpen(false)} primaryAction={{label:"Done",onPress:() => setOpen(false)}}>
<Stack gap={3}><Typography variant="h5">Leadership foundations</Typography><Typography>Six modules with practical exercises and instructor feedback.</Typography><Typography tone="muted">This read-only summary has one action and no step indicator.</Typography></Stack>
</AppModal>
</Stack>
</>
);
}
The lg width adapts to the available viewport.
'use client';
import { useState } from 'react';
import { AppModal } from '@structured-growth/sg-ui/components/AppModal';
import { Button } from '@structured-growth/sg-ui/components/Button';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
export default function ModalLGExample() {
const [open, setOpen] = useState(false);
return (
<>
<Stack gap={3}>
<Button variant="outlined" onPress={() => setOpen(true)}>Open lg dialog</Button>
<AppModal open={open} title="Course overview" subtitle="Select a size appropriate to the task." size={"lg"} showCloseButton onClose={() => setOpen(false)} primaryAction={{label:"Done",onPress:() => setOpen(false)}}>
<Stack gap={3}><Typography variant="h5">Leadership foundations</Typography><Typography>Six modules with practical exercises and instructor feedback.</Typography><Typography tone="muted">This read-only summary has one action and no step indicator.</Typography></Stack>
</AppModal>
</Stack>
</>
);
}
The xl width adapts to the available viewport.
'use client';
import { useState } from 'react';
import { AppModal } from '@structured-growth/sg-ui/components/AppModal';
import { Button } from '@structured-growth/sg-ui/components/Button';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
export default function ModalXLExample() {
const [open, setOpen] = useState(false);
return (
<>
<Stack gap={3}>
<Button variant="outlined" onPress={() => setOpen(true)}>Open xl dialog</Button>
<AppModal open={open} title="Course overview" subtitle="Select a size appropriate to the task." size={"xl"} showCloseButton onClose={() => setOpen(false)} primaryAction={{label:"Done",onPress:() => setOpen(false)}}>
<Stack gap={3}><Typography variant="h5">Leadership foundations</Typography><Typography>Six modules with practical exercises and instructor feedback.</Typography><Typography tone="muted">This read-only summary has one action and no step indicator.</Typography></Stack>
</AppModal>
</Stack>
</>
);
}
Full-screen composition for a large workspace.
'use client';
import { useState } from 'react';
import { AppModal } from '@structured-growth/sg-ui/components/AppModal';
import { Button } from '@structured-growth/sg-ui/components/Button';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
export default function ModalFULLExample() {
const [open, setOpen] = useState(false);
return (
<>
<Stack gap={3}>
<Button variant="outlined" onPress={() => setOpen(true)}>Open full dialog</Button>
<AppModal open={open} title="Course overview" subtitle="Select a size appropriate to the task." size={"full"} showCloseButton onClose={() => setOpen(false)} primaryAction={{label:"Done",onPress:() => setOpen(false)}}>
<Stack gap={3}><Typography variant="h5">Leadership foundations</Typography><Typography>Six modules with practical exercises and instructor feedback.</Typography><Typography tone="muted">This read-only summary has one action and no step indicator.</Typography></Stack>
</AppModal>
</Stack>
</>
);
}
Fields and content inherit the local density; AppModal footer actions retain their compact contract.
Not saved
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { AppModal } from '@structured-growth/sg-ui/components/AppModal';
import { Button } from '@structured-growth/sg-ui/components/Button';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
import { TextField } from '@structured-growth/sg-ui/experimental';
export default function CompactModalExample() {
const [open, setOpen] = useState(false);
const [value, setValue] = useState("Leadership foundations");
const [saved, setSaved] = useState("Not saved");
return (
<>
<ThemeScope density={"compact"}>
<Stack gap={3}>
<Button onPress={() => setOpen(true)}>Open compact form</Button><Typography role="status">{saved}</Typography>
<AppModal open={open} title="Rename course" size="sm" showCloseButton onClose={() => setOpen(false)} primaryAction={{label:"Save locally",onPress:() => {setSaved(value);setOpen(false);}}} secondaryAction={{label:"Cancel",onPress:() => setOpen(false)}}>
<TextField label="Course title" value={value} onValueChange={setValue} />
</AppModal>
</Stack>
</ThemeScope>
</>
);
}
Fields and content inherit the local density; AppModal footer actions retain their compact contract.
Not saved
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { AppModal } from '@structured-growth/sg-ui/components/AppModal';
import { Button } from '@structured-growth/sg-ui/components/Button';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
import { TextField } from '@structured-growth/sg-ui/experimental';
export default function ComfortableModalExample() {
const [open, setOpen] = useState(false);
const [value, setValue] = useState("Leadership foundations");
const [saved, setSaved] = useState("Not saved");
return (
<>
<ThemeScope density={"comfortable"}>
<Stack gap={3}>
<Button onPress={() => setOpen(true)}>Open comfortable form</Button><Typography role="status">{saved}</Typography>
<AppModal open={open} title="Rename course" size="sm" showCloseButton onClose={() => setOpen(false)} primaryAction={{label:"Save locally",onPress:() => {setSaved(value);setOpen(false);}}} secondaryAction={{label:"Cancel",onPress:() => setOpen(false)}}>
<TextField label="Course title" value={value} onValueChange={setValue} />
</AppModal>
</Stack>
</ThemeScope>
</>
);
}
Two sections share host draft state. Tab content scrolls independently; dialog scrolling remains available when chrome cannot fit. Saving here updates local state only.
No tabbed settings saved
'use client';
import { useState } from 'react';
import { AppModal } from '@structured-growth/sg-ui/components/AppModal';
import { Button } from '@structured-growth/sg-ui/components/Button';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
import { AppPageTabs } from '@structured-growth/sg-ui/components/AppPageTabs';
import { TextField } from '@structured-growth/sg-ui/experimental';
export default function TabbedModalExample() {
const [open, setOpen] = useState(false);
const [tab, setTab] = useState("details");
const [course, setCourse] = useState("Leadership foundations");
const [summary, setSummary] = useState("Build confidence through practice.");
const [saved, setSaved] = useState("No tabbed settings saved");
return (
<>
<Stack gap={3}>
<Button variant="outlined" onPress={() => setOpen(true)}>Open tabbed settings</Button><Typography role="status">{saved}</Typography>
<AppModal open={open} title="Course configuration" size="md" heightMode="md" showCloseButton onClose={() => setOpen(false)} bodyStyle={{display:"flex",flexDirection:"column",overflow:"auto"}} primaryAction={{label:"Save locally",onPress:() => {setSaved(course + ": " + summary);setOpen(false);}}} secondaryAction={{label:"Cancel",onPress:() => setOpen(false)}}>
<AppPageTabs value={tab} onChange={setTab} label="Configuration sections" style={{position:"sticky",top:0}} items={[
{id:"details",label:"Details",content:<Stack gap={3} style={{overflow:"auto",maxHeight:"35dvh"}}><TextField label="Course title" value={course} onValueChange={setCourse} /><Typography>Configure the title learners see.</Typography></Stack>},
{id:"summary",label:"Summary",content:<Stack gap={3} style={{overflow:"auto",maxHeight:"35dvh"}}><TextField label="Course summary" value={summary} onValueChange={setSummary} /><Typography>Keep the summary concise and useful.</Typography></Stack>}
]} />
</AppModal>
</Stack>
</>
);
}
Step labels appear only for a multi-step task. Back preserves the draft; reopening begins at step one. Persistence remains host-owned.
No course created
'use client';
import { useState } from 'react';
import { AppModal } from '@structured-growth/sg-ui/components/AppModal';
import { Button } from '@structured-growth/sg-ui/components/Button';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
import { TextField } from '@structured-growth/sg-ui/experimental';
export default function TwoStepModalExample() {
const [open, setOpen] = useState(false);
const [step, setStep] = useState(1);
const [name, setName] = useState("Leadership foundations");
const [saved, setSaved] = useState("No course created");
return (
<>
<Stack gap={3}>
<Button onPress={() => {setStep(1);setOpen(true);}}>Start two-step flow</Button><Typography role="status">{saved}</Typography>
<AppModal open={open} title="Create course" size="md" showCloseButton onClose={() => setOpen(false)} steps={{current:step,total:2,label:step === 1 ? "1 of 2: Course details" : "2 of 2: Review"}} primaryAction={{label:step === 1 ? "Review" : "Create locally",disabled:!name.trim(),onPress:() => {if (step === 1) setStep(2);else {setSaved("Created " + name);setOpen(false);}}}} secondaryAction={{label:step === 1 ? "Cancel" : "Back",onPress:() => {if (step === 1) setOpen(false);else setStep(1);}}}>
{step === 1 ? <TextField label="New course title" value={name} onValueChange={setName} /> : <Stack gap={3}><Typography variant="h5">{name}</Typography><Typography>Review your title before creating this local draft.</Typography></Stack>}
</AppModal>
</Stack>
</>
);
}
Long content uses bounded dialog height and an explicit host footer. Focus and dismissal remain owned by AppModal.
'use client';
import { useState } from 'react';
import { AppModal } from '@structured-growth/sg-ui/components/AppModal';
import { Button } from '@structured-growth/sg-ui/components/Button';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
export default function ScrollableModalExample() {
const [open, setOpen] = useState(false);
return (
<>
<Stack gap={3}>
<Button variant="outlined" onPress={() => setOpen(true)}>Open long course outline</Button>
<AppModal open={open} title="Course outline" subtitle="12 practical modules" size="lg" heightMode="lg" showCloseButton onClose={() => setOpen(false)} footerContent={<Button variant="outlined" onPress={() => setOpen(false)}>Close outline</Button>}>
<Stack gap={4}><Typography>{"Module 1: Practice, reflect, and apply the learning to a real situation."}</Typography>
<Typography>{"Module 2: Practice, reflect, and apply the learning to a real situation."}</Typography>
<Typography>{"Module 3: Practice, reflect, and apply the learning to a real situation."}</Typography>
<Typography>{"Module 4: Practice, reflect, and apply the learning to a real situation."}</Typography>
<Typography>{"Module 5: Practice, reflect, and apply the learning to a real situation."}</Typography>
<Typography>{"Module 6: Practice, reflect, and apply the learning to a real situation."}</Typography>
<Typography>{"Module 7: Practice, reflect, and apply the learning to a real situation."}</Typography>
<Typography>{"Module 8: Practice, reflect, and apply the learning to a real situation."}</Typography>
<Typography>{"Module 9: Practice, reflect, and apply the learning to a real situation."}</Typography>
<Typography>{"Module 10: Practice, reflect, and apply the learning to a real situation."}</Typography>
<Typography>{"Module 11: Practice, reflect, and apply the learning to a real situation."}</Typography>
<Typography>{"Module 12: Practice, reflect, and apply the learning to a real situation."}</Typography></Stack>
</AppModal>
</Stack>
</>
);
}