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 = initialState
this.handleSubmit = this.handleSubmit.bind(this)
this.handleChange = this.handleChange.bind(this)
this.reset = this.reset.bind(this)
}
handleChange(evt) {
const { name, value } = evt.target
this.setState({
[name]: value
})
}
handleSubmit(evt) {
evt.preventDefault()
const errors = validate(this.state)
const hasErrors = Object.keys(errors).length > 0
this.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 } = this
const errorMsg = name => state.errors[name]
const isError = name => !!errorMsg(name)
const hasErrors = Object.keys(state.errors).length > 0
return (
<div style={{ position: 'relative' }}>
{state.isSubmitted && (
<Banner
color={Banner.colors.green}
style={{ position: 'absolute', top: '0', left: '0' }}
>
Course created!
</Banner>
)}
{hasErrors && (
<Banner
color={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>
<TextInput
error={isError('name')}
onChange={this.handleChange}
name="name"
label="Course name"
placeholder="Title"
subLabel={errorMsg('name') || 'Use Title Case'}
value={state.name}
/>
<Dropdown
error={isError('level')}
label="Course difficulty level"
placeholder="Choose one"
subLabel={errorMsg('level')}
menu={
<>
{['beginner', 'intermediate', 'advanced'].map(level => (
<Dropdown.Item
name="level"
key={level}
onClick={_ => this.setState({ level })}
>
{capitalize(level)}
</Dropdown.Item>
))}
</>
}
/>
<DatePicker
error={isError('publish')}
name="publish"
value={state.publish}
label="Publish date"
subLabel="When your course will go live"
onSelect={publish => this.setState({ publish })}
/>
<Switch
error={isError('slides')}
checked={state.slides}
name="slides"
onClick={checked => this.setState({ slides: checked })}
>
Has slides?
</Switch>
<Form.Divider />
<Radio.Group
error={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 />
<Checkbox
error={isError('demo')}
checked={state.demo}
name="demo"
label="Demo included"
value="demo"
onCheck={(_, checked, __, name) =>
this.setState({ [name]: checked })
}
/>
<Checkbox
error={isError('assessment')}
checked={state.assessment}
name="assessment"
label="Assessment included"
value="assessment"
onCheck={(_, checked, __, name) =>
this.setState({ [name]: checked })
}
/>
<TextArea
error={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>
<Button
appearance={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

For short textual input.Docs

Checkbox

For multiple selection.Docs

Switch

For on/off selections.Docs

DatePicker

For dates.Docs

Tag

For dynamic lists.Docs
javascript
flowtype
static-types

Guidelines

Write labels in sentence case. Uppercase is not optimal for scannability.

Do
Don't

Don't rely on the placeholder. Use the label to specify the purpose of each field to avoid usability issues.

Do
Don't

Specify errors inline. Show where the error occurs and a clear reason for the error so users can find it in context.

Do

Email address is not valid

Don't

Rather than implying a required field with a marker (*), consider noting optional fields instead.

Do
Don't

Use Form.Divider to group similar fields together into logical groups for users to parse the form more effectively.

Do
Don't

Accessibility

WCAG 2.1 AA Compliance

100% axe-core tests
Manual audit

WAI-ARIA Patterns: Form (landmark region)

Props

Form.VerticalLayout

Name
Type
Description
Default
children
Required
ReactNodeform elements that will be aligned and spaced

Form.ButtonRow

Name
Type
Description
Default
align
left | right
horizontal alignment (from Form.ButtonRow.aligns)left
children
Required
ReactNodebuttons to be aligned