Documentation

  • Building your first component
  • Managing design tokens
  • Version Control
  • Using Visly components in code
  • Styling layers
  • Designing Primitives
  • Interactive components
  • Props
  • Variants
  • Composition
  • Responsive design

Tutorials

  • Building stateful components
  • Migrating components from code
  • Integrate Visly with Next.js
  • Building an Avatar component
  • Using slots for dynamic content
  • Using variants to customize layout
  • Stacking layers in z-index

Documentation

Designing Primitives

Primitives in Visly allow you to build more complex components that come with behaviors out of the box. As opposed to building a component from scratch, you can choose to start from any of our built-in primitives and style them however you'd like. They come with a deep level of customization and we ensure that they're accessible and output correct semantic html.

Primitives with built-in logic

An example is the Radio primitive. It allows you to build a group of radio buttons that handles keyboard interaction and focus management internally. It can be created by clicking the [+] button next to the Primitives header in the main view.

Locked layers

Primitives are styled just like other components. However, they might have certain restrictions to ensure that they function properly when used in code. The Radio has an inner component (Composition), Button. This layer is locked indicated by the icon, meaning it can't be removed.

Inherent variants

When selecting the inner component Button, you'll notice it has a built-in Variant that's also locked: selected. Since we internally track state of which Button in the Radio is currently selected, this variant is essential. It is styled just like any other variant, but cannot be removed or renamed.

Primitive component API:s

Primitives will have certain built-in props and interactions that differ from a normal component. The mentioned selected variant is applied to the Button with the same value. In the below example, selected is set to a, meaning that the first Button will have selected styling. This way, the radio button is completely controlled, but allows us to manage focus and keyboard interactions correctly so you don't have to. The Radio together with its Radio.Button can be used in code as such (Designing Primitives).

import { Radio } from 'visly'

// static number of buttons
function MyComponent() {
  const [selected, setSelected] = useState('a')
  return (
    <Radio selected={selected} onSelect={setSelected}>
      <Radio.Button value='a' text='Option A'/>
      <Radio.Button value='b' text='Option B'/>
    </Radio>
  )
}

// dynamic number of buttons
function MyComponent({ items }) {
  const [selected, setSelected] = useState(items[0].value)
  return (
    <Radio selected={selected} onSelect={setSelected}>
		  { items.map(i => <Radio.Button value={i.value} text={i.text} />}
    </Radio>
  )
}

Tips & tricks

Most of our primitives encapsulate a certain behavior, but they can be customized a lot. A Radio doesn't need to look like an ordinary radio group; it's a perfect fit for any component that needs the same behavior as a radio group.

Primitives

All primitives share the common props listed here. Notably, className and style will allow you to always use visly components, including primitives, in conjuction with popular css-in-js libraries such as styled-components.

PropTypeDescription
disabledbooleanSets the disabled state of the component
classNamestringAdditional classes to apply
styleCSSPropertiesInlines styles to apply
tabIndexnumberThe tab index to apply
innerRefReact.Ref<HTMLDivElement>Reference to the root element of the component
rolestringHTML role to apply to root element
testIdstringPassed to data-testidquery for UI tests
onKeyUp(event: React.KeyboardEvent<any>) => voidCallback for key up events
onKeyDown(event: React.KeyboardEvent<any>) => voidCallback for key down events

Button

A simple button component. Buttons can easily be built starting from a blank canvas, since those always expose an onClick prop, but using the Button primitive adds the HTML semantics of <button/> and comes with some default button styling.

Props

PropTypeDescription
onClick() => voidCallback for when the button is clicked

Usage

<Button onClick={alert('hello')} />

Input

The common text input component. The Input primitive has a root layer, which is used to style the container of the input, and a Text layer used to style the text in the input.

Props

PropTypeDescription
valuestringThe value of the Input
inputRefReact.Ref<HTMLInputElement>A reference to the input element
onChange(value: string) => voidCallback for when the input text is changed
onFocus() => voidCallback for when the input receives focus
onBlur() => voidCallback for when the input loses focus

Usage

import { Input } from 'visly'

function MyComponent() {
  const [value, setValue] = useState('')

  return <Input value={value} onChange={setValue} />
}

Checkbox, Switch & ToggleButton

Checkbox, Switch and ToggleButton primitives are very similar. Components built with these primitives have a locked variant, checked, that cannot be removed. It is mapped to the checked prop, which controls the state of the component.

Props

PropTypeDescription
checkedbooleanControls the state of the component
onChange(checked: boolean) => voidCallback for when the component is interacted with

Usage

import { Checkbox, Switch, ToggleButton } from 'visly'

function MyComponent() {
  const [checked, setChecked] = useState(false)

  return (
    <>
      <Checkbox checked={checked} onChange={setChecked}/>
      <Switch checked={checked} onChange={setChecked}/>
      <ToggleButton checked={checked} onChange={setChecked}/>
    </>
  )
}

Radio

A radio group component. This primitive exports two components, Radio and Radio.Button, to be passed in as its children. See the Composition for more info.

The Button has a "selected" variant that is applied automatically by the parent if the value prop of that button matches the selected prop of the parent Radio.

Props

Radio

PropTypeDescription
selectedstringThe selected value in the radio group. There should be a matching Radio.Button with this value.
onSelect(value: string) => voidCallback for when a radio button in the group is selected. It is passed the corresponding value of the selected Radio.Button

Radio.Button

PropTypeDescription
valuestringThe value of the radio button
selectedbooleanControls display the selected variant styling (This automatically set by parent and should in most cases not be used)

Usage

import { Radio } from 'visly'

// Static amount of buttons
function MyComponent(props: Props) {
  const [selected, setSelected] = useState('a')
  return (
    <Radio selected={selected} onSelect={setSelected}>
      <Radio.Button value='a' text='Option A'/>
      <Radio.Button value='b' text='Option B'/>
    </Radio>
  )
}

// Dynamic amount of buttons
interface Props {
  options: {
    value: string
    label: string
  }[]
}

function MyComponent({ options }: Props) {
  const [selected, setSelected] = useState(options[0].value)

  return (
    <Radio selected={selected} onSelect={setSelected}>
      { options.map(o => <Radio.Button value={o.value} text={o.label} />)}
    </Radio>
  )
}

Segmented

The segmented control primitive works exactly the same as the Radio primitive; the only difference is in styling. See the section of Radio primitive for details on props and usage.

Progress bar

A simple progress bar component.

Props

PropTypeDescription
valuenumberA value between 0 and 1 that determines the size of the filled area

Usage

<ProgressBar value={0.5} />

Tooltip

Useful for displaying a hint with some text when hovering over an element. Renders in a portal on the document body when the child element is hovered over.

Props

PropTypeDescription
labelstringThe text to display in the tooltip
childrenReact.ReactNodeThe element triggering showing the tooltip when hovered over
gravitylefttoprightbottomControls the position of the tooltip (default='bottom')
delayMsnumberDelay until the tooltip shows (default=400)

Usage

import { Tooltip } from 'visly'

function MyComponent() {
  return (
    <Tooltip gravity='right' text='hello'>
      <MyOtherComponent />
    </Tooltip>
  )
}

Popover

A component that displays some content when another element is clicked. Its position gravity will automatically readjust if the component does not fit on the screen.

Props

PropTypeDescription
targetRefReact.RefObject<any>The target element to show the popover when clicked. The popover will also use this element as anchor when determining its position
isOpenbooleanControls if the popover should be visible
onClickOutside() => voidIf this is present, the popover will eat up events outside of it, and call this handler on a click event
gravitylefttoprightbottomControls the position of the tooltip (default='bottom')
alignstartcenterendControls the alignment of the popover (default='start'
gravityOffsetnumberThe gravity offset in px to apply
alignOffsetnumberThe alignment offset in px to apply

Usage

import { Button, Popover } from 'visly'

function MyComponent() {
  const [isOpen, setIsOpen] = useState(false)
  const ref = useRef()

  return (
    <>
      <Button innerRef={ref} onClick={() => setIsOpen(true)}>
        Open popover
      </Button>
      <Popover targetRef={ref} isOpen={isOpen} onClickOutside={() => setIsOpen(false)} gravityOffset={10} />
    </>
  )
}

Dialog

A typical dialog/modal component that displays content in the center of the screen and traps focus.

Props

PropTypeDescription
isOpenbooleanControls if the dialog should be visible
onClose() => voidCallback for when the user clicks outside of the dialog

Usage

import { Button, Dialog } from 'visly'

function MyComponent() {
  const [isOpen, setIsOpen] = useState(false)

  return (
    <>
      <Button onClick={() => setIsOpen(true)}>
        Open dialog
      </Button>
      <Dialog isOpen={isOpen} onClose={() => setIsOpen(false)}/>
    </>
  )
}

Select

A select component consists of a button that triggers a listbox of options to show when clicked. This primitive exports two components, Select and Select.Option, to be passed in as its children. See the Composition for more info.

The Select component internally handles keyboard navigation for focusing and selecting options, as well as applying aria labels.

The component has a selected variant which is automatically applied to a Select.Option if the value of that option matches the selected prop of the parent Select.

The default version of this primitive includes a text layer in both the Button and Option that's mapped to a label prop. It's common practice to display some text matching the selected option in the button.

Keyboard shortcuts

KeyAction
upIf open, moves focus to the previous option if available. If closed, opens the select
downIf open, moves focus to the previous option if available. If closed, opens the select
enterSelects the currently focused element and closes the select
escapeCloses the select without selecting any option

Props

Select

PropTypeDescription
selectedstringThe value for the selected option
onSelect(value: string) => voidCallback for when an option is selected. It is passed the corresponding value of that option

Select.Option

PropTypeDescription
valuestringThe assigned value for the option
selectedbooleanControls display the selected variant styling (This automatically set by parent and should in most cases not be used)

Usage

import { Select } from 'visly'

interface Props {
  options: {
    value: string
    label: string
  }[]
}

function MyComponent({ options }: Props) {
  const [selected, setSelected] = useState(options[0])

  return (
    <Select 
        selected={selected.value} 
        label={selected.label} 
        onSelect={v => setSelected(options.find(o => o.value === v))}>
      {options.map(o => <Select.Option value={o.value} label={o.label} />)}
    </Select>
  )
}

Slider

A typical slider component.

Props

PropTypeDescription
valuenumberControls the value of the slider
onChange(value: number) => voidCallback for when the slider is interacted with
minnumberThe minimum value of the slider
maxnumberThe maximum value of the slider
stepnumberControls how much the value should change in each step. The total range (max - min) should be divisible by this value.

Usage

import { Slider } from 'visly'

function MyComponent() {
  const [value, setValue] = useState(0)

  return <Slider value={value} onChange={setValue} min={0} max={100} step={10}/>
}