Form
The Form component helps arrange a form full of input controls. The package doesn't provide these controls but it is compatible with them.
- Install
npm install @pluralsight/ps-design-system-form
- Import
import Form from '@pluralsight/ps-design-system-form'
Examples
In-app
import React from 'react'import Form from '@pluralsight/ps-design-system-form'import Button from '@pluralsight/ps-design-system-button'import Banner from '@pluralsight/ps-design-system-banner'import Checkbox from '@pluralsight/ps-design-system-checkbox'import DatePicker from '@pluralsight/ps-design-system-datepicker'import Dropdown from '@pluralsight/ps-design-system-dropdown'import { Heading } from '@pluralsight/ps-design-system-text'import TextInput from '@pluralsight/ps-design-system-textinput'import TextArea from '@pluralsight/ps-design-system-textarea'import Radio from '@pluralsight/ps-design-system-radio'import Switch from '@pluralsight/ps-design-system-switch'import { capitalize } from '@pluralsight/ps-design-system-util'const validate = state => {const rules = {name: { rule: /.+/, message: 'Required' },level: {rule: /beginner|intermediate|advanced/,message: 'Select a valid option'},slides: {rule: /false|true/,message: 'Turn on or off'},demo: {rule: /false|true/,message: 'Select a demo option'},assessment: {rule: /false|true/,message: 'Select an assessment option'},desc: { rule: /.+/, message: 'Required' },publish: { rule: /^\d{2}\/\d{2}\/\d{4}$/, message: 'Required' }}return Object.keys(rules).reduce((errors, ruleName) => {if (rules[ruleName].rule.test(state[ruleName])) {delete errors[ruleName]} else {errors[ruleName] = rules[ruleName].message}return errors},{ ...state.errors })}const initialState = {errors: {},isSubmitting: false,isSubmitted: false,name: '',level: null,slides: false,slidestech: null,demo: false,assessment: false,desc: '',publish: undefined}class InAppExample extends React.Component {constructor() {super()this.state = initialStatethis.handleSubmit = this.handleSubmit.bind(this)this.handleChange = this.handleChange.bind(this)this.reset = this.reset.bind(this)}handleChange(evt) {const { name, value } = evt.targetthis.setState({[name]: value})}handleSubmit(evt) {evt.preventDefault()const errors = validate(this.state)const hasErrors = Object.keys(errors).length > 0this.setState({ errors })if (!hasErrors) {this.setState({ isSubmitting: true }, _ => {setTimeout(_ =>this.setState({ isSubmitting: false, isSubmitted: true }, _ =>setTimeout(_ => this.reset(), 800)),1500)})}}reset(evt) {this.setState(initialState)}render() {const { state } = thisconst errorMsg = name => state.errors[name]const isError = name => !!errorMsg(name)const hasErrors = Object.keys(state.errors).length > 0return (<div style={{ position: 'relative' }}>{state.isSubmitted && (<Bannercolor={Banner.colors.green}style={{ position: 'absolute', top: '0', left: '0' }}>Course created!</Banner>)}{hasErrors && (<Bannercolor={Banner.colors.red}style={{ position: 'absolute', top: '0', left: '0' }}>Failed: Sample course form has errors</Banner>)}<form onSubmit={this.handleSubmit}><Form.VerticalLayout><Heading><h2>Course sample form</h2></Heading><TextInputerror={isError('name')}onChange={this.handleChange}name="name"label="Course name"placeholder="Title"subLabel={errorMsg('name') || 'Use Title Case'}value={state.name}/><Dropdownerror={isError('level')}label="Course difficulty level"placeholder="Choose one"subLabel={errorMsg('level')}menu={<>{['beginner', 'intermediate', 'advanced'].map(level => (<Dropdown.Itemname="level"key={level}onClick={_ => this.setState({ level })}>{capitalize(level)}</Dropdown.Item>))}</>}/><DatePickererror={isError('publish')}name="publish"value={state.publish}label="Publish date"subLabel="When your course will go live"onSelect={publish => this.setState({ publish })}/><Switcherror={isError('slides')}checked={state.slides}name="slides"onClick={checked => this.setState({ slides: checked })}>Has slides?</Switch><Form.Divider /><Radio.Grouperror={isError('slidestech')}disabled={!state.slides}name="slidestech"onSelect={(_, slidestech) => this.setState({ slidestech })}value={state.slidestech}><Radio.Button value="key" label="Keynote" /><Radio.Button value="pptx" label="Powerpoint" /></Radio.Group><Form.Divider /><Checkboxerror={isError('demo')}checked={state.demo}name="demo"label="Demo included"value="demo"onCheck={(_, checked, __, name) =>this.setState({ [name]: checked })}/><Checkboxerror={isError('assessment')}checked={state.assessment}name="assessment"label="Assessment included"value="assessment"onCheck={(_, checked, __, name) =>this.setState({ [name]: checked })}/><TextAreaerror={isError('desc')}label="Description"subLabel={errorMsg('desc')}placeholder="What is your course about?"onChange={this.handleChange}name="desc"value={state.desc}/><Form.ButtonRow><Button loading={state.isSubmitting} onClick={this.handleSubmit}>Save</Button><Buttonappearance={Button.appearances.secondary}onClick={evt => evt.preventDefault()}>Cancel</Button></Form.ButtonRow></Form.VerticalLayout></form></div>)}}export default InAppExample
Form.VerticalLayout
Forms should be horizontally left-aligned, with one input control per line, and inputs are stretched to the full container width. Bring your own container. Form.VerticalLayout takes care of the vertical spacing between controls.
import React from 'react'import Form from '@pluralsight/ps-design-system-form'import TextInput from '@pluralsight/ps-design-system-textinput'const Comp = () => (<div style={{ width: '80%' }}><Form.VerticalLayout><TextInput placeholder="Stacked" /><TextInput placeholder="In a form" /></Form.VerticalLayout></div>)export default Comp
Form.Divider
Dividers are simple visual indicators that break form flow and content into logical pieces.
import React from 'react'import Form from '@pluralsight/ps-design-system-form'import TextInput from '@pluralsight/ps-design-system-textinput'const Comp = () => (<div style={{ width: '80%' }}><Form.VerticalLayout><TextInput placeholder="Related stuff" /><Form.Divider /><TextInput placeholder="Other related stuff" /></Form.VerticalLayout></div>)export default Comp
Form.ButtonRow
Forms commonly have multiple buttons that a user can interact with. Those buttons should be on a single row and left-aligned. Form.ButtonRow will help.
import React from 'react'import Form from '@pluralsight/ps-design-system-form'import TextInput from '@pluralsight/ps-design-system-textinput'import Button from '@pluralsight/ps-design-system-button'const Comp = () => (<div style={{ width: '80%' }}><Form.VerticalLayout><TextInput placeholder="Related stuff" /><TextInput placeholder="Other related stuff" /><Form.ButtonRow><Button>Primary</Button><Button appearance={Button.appearances.secondary}>Secondary</Button></Form.ButtonRow></Form.VerticalLayout></div>)export default Comp
Form controls
Each one of these controls is a separate package. They are mean to be used inside the Form
layout component. Find links to the docs below
Text Input
Checkbox
Switch
DatePicker
Tag
Guidelines
Write labels in sentence case. Uppercase is not optimal for scannability.
Don't rely on the placeholder. Use the label to specify the purpose of each field to avoid usability issues.
Specify errors inline. Show where the error occurs and a clear reason for the error so users can find it in context.
Rather than implying a required field with a marker (*), consider noting optional fields instead.
Use Form.Divider
to group similar fields together into logical groups for users to parse the form more effectively.
Accessibility
WCAG 2.1 AA Compliance
100% axe-core testsManual audit
WAI-ARIA Patterns: Form (landmark region)
Props
Form.VerticalLayout
Name | Type | Description | Default |
---|---|---|---|
children Required | ReactNode | form elements that will be aligned and spaced |
|
Form.ButtonRow
Name | Type | Description | Default |
---|---|---|---|
align |
| horizontal alignment (from Form.ButtonRow.aligns) | left |
children Required | ReactNode | buttons to be aligned |
|