Attribute Selection Block
AttributeSelection is a controlled checkbox-selection form for storing selected { key, value } attributes.
Installation
- npm
- yarn
- pnpm
- bun
npm install @nodeblocks/frontend-attribute-selection-block
yarn add @nodeblocks/frontend-attribute-selection-block
pnpm add @nodeblocks/frontend-attribute-selection-block
bun add @nodeblocks/frontend-attribute-selection-block
What You Need
| Item | Why it matters |
|---|---|
data | Controlled form data. Default shape is { attributes: [] } |
onDataChange | Receives the next data object whenever a checkbox changes |
options (optional) | Checkbox choices rendered by the default CheckboxList |
labels (optional) | Copy for title, subtitle, field name, and submit button |
layout (optional) | Checkbox list layout: one-column or responsive grid |
onSubmit (optional) | Inherited form submit handler |
AttributeSelection does not own selection state. Store data in your app, pass it back to the block, and update it from onDataChange. Selected items are stored in data.attributes as { key, value } pairs.
Code Examples
- Quick Start
- Labels and Layout
- Compound Components
- Block Override
function Example() { const defaultData = {attributes: []}; const [data, setData] = React.useState(defaultData); const options = [ {key: 'role', value: 'frontend', label: 'Frontend engineer'}, {key: 'role', value: 'backend', label: 'Backend engineer'}, {key: 'role', value: 'fullstack', label: 'Full-stack engineer'}, {key: 'role', value: 'data', label: 'Data engineer'}, ]; const labels = { attributeSelectionTitle: 'Role', subtitle: 'Select the roles that match your profile', fieldName: 'Role options', submitButton: 'Save', }; return ( <AttributeSelection data={data} onDataChange={setData} options={options} labels={labels} layout="grid" onSubmit={event => { event.preventDefault(); setData((current) => ({ ...current })); }} /> ); }
Customize default copy and switch the checkbox list between one-column and grid layouts.
function Example() { const defaultData = {attributes: [{key: 'department', value: 'engineering'}]}; const [data, setData] = React.useState(defaultData); return ( <AttributeSelection data={data} onDataChange={setData} layout="one-column" labels={{ attributeSelectionTitle: 'Department', subtitle: 'Choose the departments this user can work with', fieldName: 'Departments', submitButton: 'Continue', }} options={[ {key: 'department', value: 'engineering', label: 'Engineering'}, {key: 'department', value: 'design', label: 'Design'}, {key: 'department', value: 'operations', label: 'Operations'}, ]} onSubmit={event => event.preventDefault()} /> ); }
There are no URL props in this block. Use root labels for the default blocks, or pass children directly to Title, Subtitle, FieldName, and SubmitButton when composing your own layout.
Use named child blocks when you need a custom layout or manual checkbox groups.
function Example() { const defaultData = {attributes: []}; const [data, setData] = React.useState(defaultData); return ( <AttributeSelection data={data} onDataChange={setData} onSubmit={event => event.preventDefault()} sx={{maxWidth: 560, mx: 'auto'}} > <div style={{display: 'grid', gap: 32}}> <AttributeSelection.Title sx={{textAlign: 'left'}}> Skills </AttributeSelection.Title> <AttributeSelection.Subtitle> Select every skill that applies. </AttributeSelection.Subtitle> <div style={{display: 'grid', gap: 12}}> <AttributeSelection.FieldName>Engineering skills</AttributeSelection.FieldName> <div style={{display: 'grid', gap: 12}}> <AttributeSelection.CheckboxField name="skill" value="react" label="React" /> <AttributeSelection.CheckboxField name="skill" value="typescript" label="TypeScript" /> <AttributeSelection.CheckboxField name="skill" value="testing" label="Testing" /> </div> </div> <AttributeSelection.SubmitButton disabled={!data.attributes.length}> Save skills </AttributeSelection.SubmitButton> </div> </AttributeSelection> ); }
Use function children to replace default blocks and add small helper blocks inside the default field area.
function Example() { const defaultData = {attributes: []}; const [data, setData] = React.useState(defaultData); return ( <AttributeSelection data={data} onDataChange={setData} labels={{ attributeSelectionTitle: 'Profile attributes', subtitle: 'Pick the attributes that should be saved.', fieldName: 'Attributes', submitButton: 'Save', }} options={[ {key: 'profile', value: 'mentor', label: 'Mentor'}, {key: 'profile', value: 'reviewer', label: 'Reviewer'}, {key: 'profile', value: 'speaker', label: 'Speaker'}, ]} layout="grid" onSubmit={event => event.preventDefault()} > {({defaultBlocks}) => ({ blocks: { ...defaultBlocks, helperText: ( <div style={{fontSize: 13, color: '#666'}}> Selected attributes: {data.attributes.length} </div> ), submitButton: ( <AttributeSelection.SubmitButton disabled={!data.attributes.length}> Save profile </AttributeSelection.SubmitButton> ), }, blockOrder: ['attributeSelectionTitle', 'subtitle', 'fieldName', 'checkboxList', 'helperText', 'submitButton'], })} </AttributeSelection> ); }
When to use block overrides
Use overrides when the default title, checkbox list, and submit button are useful but you need to replace one block or add helper content. defaultBlockOrder is attributeSelectionTitle, subtitle, fieldName, checkboxField, checkboxList, submitButton; the root render filters out checkboxField, so the visible default flow is title, subtitle, field name, checkbox list, then submit button.
Important Props
Core Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
data | AttributeSelectionFormData | Yes | - | Controlled data object: { attributes: { key: string; value: string }[] } |
onDataChange | (data: AttributeSelectionFormData, meta: { name: string; value: unknown; cause: 'input' | 'change' | 'blur' | 'clear' | 'reset' | 'programmatic'; event?: SyntheticEvent }) => void | Yes | - | Called when selection changes. CheckboxField uses cause: 'change' and name: 'attributes' |
options | AttributeSelectionOption[] | No | undefined | Options rendered by CheckboxList; each option is { key, value, label } |
errors | { [fieldName: string]: string | string[] } | No | undefined | Stored in context for custom blocks; default UI blocks do not render errors |
layout | 'one-column' | 'grid' | No | 'one-column' | Controls CheckboxList layout |
Content Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
labels | { attributeSelectionTitle?: ReactNode; subtitle?: ReactNode; fieldName?: ReactNode; submitButton?: ReactNode } | No | undefined | Copy consumed by default title, subtitle, field name, and submit button |
labels.attributeSelectionTitle | ReactNode | No | undefined | Title text rendered by AttributeSelection.Title |
labels.subtitle | ReactNode | No | undefined | Subtitle text rendered by AttributeSelection.Subtitle |
labels.fieldName | ReactNode | No | undefined | Field label rendered above the checkbox list |
labels.submitButton | ReactNode | No | '保存' | Submit button content when no children are passed |
Subcomponents:
| Sub-component | Main Props | Inherits | Built on |
|---|---|---|---|
AttributeSelection.Title | children, labels.attributeSelectionTitle | TypographyProps | Typography |
AttributeSelection.Subtitle | children, labels.subtitle | TypographyProps | Typography |
AttributeSelection.FieldName | children, labels.fieldName | TypographyProps | Typography |
AttributeSelection.CheckboxList | options, layout, children | StackProps | Stack |
AttributeSelection.SubmitButton | children, labels.submitButton | ButtonProps | Button |
Layout and Composition Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children | BlocksOverride | ReactNode | No | undefined | Compound children or function override returning blocks and blockOrder |
className | string | No | undefined | Class on the root form (nbb-attribute-selection) |
sx | SxProps | No | undefined | MUI system styles for the root form |
onSubmit | FormEventHandler | No | undefined | Inherited form submit handler |
AttributeSelection inherits StackProps except children. The root renders as component="form" and forwards non-context Stack/form props.
Default UI Blocks
| Block | Built on | Notes |
|---|---|---|
AttributeSelection (root) | Stack | Root form with component="form" |
AttributeSelection.Title | Typography | Uses labels.attributeSelectionTitle; no built-in fallback text |
AttributeSelection.Subtitle | Typography | Uses labels.subtitle; no built-in fallback text |
AttributeSelection.FieldName | Typography | Uses labels.fieldName; no built-in fallback text |
AttributeSelection.CheckboxList | Stack | Maps options into checkboxes; layout="grid" uses responsive 2/3/4-column grid |
AttributeSelection.SubmitButton | Button | variant="contained", size="large", fullWidth, type="submit", default text '保存' |
Extra field primitives
| Primitive | Built on | Notes |
|---|---|---|
AttributeSelection.CheckboxField | FormControlLabel + Checkbox | Manual checkbox primitive for custom flows. Pass name, value, and label; toggling adds/removes { key: name, value } in data.attributes. Default control is a MUI checkbox with sx={{ p: 0 }} |
TypeScript
import {
AttributeSelection,
type AttributeSelectionFormData,
type AttributeSelectionOption,
} from '@nodeblocks/frontend-attribute-selection-block';
const options: AttributeSelectionOption[] = [
{key: 'role', value: 'frontend', label: 'Frontend engineer'},
{key: 'role', value: 'backend', label: 'Backend engineer'},
];
const [data, setData] = React.useState<AttributeSelectionFormData>({
attributes: [],
});
<AttributeSelection
data={data}
onDataChange={setData}
options={options}
labels={{attributeSelectionTitle: 'Role', fieldName: 'Roles', submitButton: 'Save'}}
/>;