react-circular-input

A declarative and composable approach means we have a lot of flexibility, here are a few examples that showcase it.

Or play around with the examples at CodeSandbox

Edit react-circular-input

Default

An example of using the 3 components included with their default styling.

const [value, setValue] = useState(0.25)

return (
	<CircularInput value={value} onChange={setValue}>
		<CircularTrack />
		<CircularProgress />
		<CircularThumb />
	</CircularInput>
)

Animated

You can use libraries like react-spring to add animation.

const [value, setValue] = useState(0.25)

return (
	<Spring to={{ value }}>
		{props => (
			<CircularInput value={props.value} onChange={setValue}>
				<CircularTrack />
				<CircularProgress />
				<CircularThumb />
			</CircularInput>
		)}
	</Spring>
)

Styled

The recommended way to style the components is to use CSS classes or CSS-in-JS as you normally do for other components 🙂

CircularTrack, CircularProgress and CircularThumb are just SVG <circle /> so you can also just tweak most (see Component docs) attributes 💅

const [value, setValue] = useState(0.25)

return (
	<StyledCircularInput value={value} onChange={setValue}>
		{/* CSS-in-JS */}
		<StyledCircularTrack />

		{/* CSS class */}
		<CircularProgress className="custom-progress" />

		{/* SVG style prop */}
		<CircularThumb r={10} fill="rgba(255,255,255,0.5)" />
	</StyledCircularInput>
)

Custom Component

Using the provided Hooks you can create your own components! 🤩

25
const CustomComponent = () => {
	const { getPointFromValue, value } = useCircularInputContext()
	const { x, y } = getPointFromValue()

	return (
		<text x={x} y={y} style={{ pointerEvents: 'none' }}>
			{Math.round(value * 100)}
		</text>
	)
}

const CustomComponentExample = () => {
	const [value, setValue] = useState(0.25)

	return (
		<CircularInput value={value} onChange={setValue}>
			<CircularProgress />
			<CircularThumb />

			{/* Add any component and use the provided hooks! */}
			<CustomComponent />

		</CircularInput>
	)
}

Progress

Can be used to simply display progress/gauge.

<CircularInput value={Math.random()}>
	<CircularTrack strokeWidth={5} stroke="#eee" />
	<CircularProgress stroke={`hsl(${props.value * 100}, 100%, 50%)`} />
</CircularInput>

Min, Max & Scale

There are no props for min/max/etc as you can easily achieve these and much more with simple code.

25%
const [value, setValue] = useState(0.25)

// custom limits
const min = 0.25
const max = 0.75

// get value within limits
const valueWithinLimits = v => Math.min(Math.max(v, min), max)

// custom range
const range = [0, 100]

// scaled range value
const rangeValue = value * (range[1] - range[0]) + range[0]

return (
	<CircularInput
		value={valueWithinLimits(value)}
		onChange={v => setValue(valueWithinLimits(v))}
	>
		<CircularTrack />
		<CircularProgress />

		{/* limit lines */}
		<line x1={-10} x2={10} y1={100} y2={100} stroke="black" />
		<line x1={190} x2={210} y1={100} y2={100} stroke="black" />

		<CircularThumb />

		{/* range value in center */}
		<text x={100} y={100} textAnchor="middle" dy="0.3em" fontWeight="bold">
			{Math.round(rangeValue)}%
		</text>
	</CircularInput>
)

Steps

You're in full control of the value so it's easy to introduce the stepped interaction.

30%
const [value, setValue] = useState(0.25)

const stepValue = v => Math.round(v * 10) / 10

return (
	<CircularInput
		value={stepValue(value)}
		onChange={v => setValue(stepValue(v))}
	>
		<CircularTrack />
		<CircularProgress />
		<CircularThumb />

		<text x={100} y={100} textAnchor="middle" dy="0.3em" fontWeight="bold">
			{Math.round(stepValue(value) * 100)}%
		</text>
	</CircularInput>
)

Readonly

Omitting the onChange prop makes it readonly.

<CircularInput value={0.25}>
	<CircularTrack />
	<CircularProgress />
</CircularInput>

Render Prop

Use children prop as a function to receive the current context.

wee!
const [value, setValue] = useState(0.25)

return (
	<CircularInput value={value} onChange={setValue}>
		{({ getPointFromValue }) => (
			<>
				<CircularTrack />
				<CircularProgress />

				<text {...getPointFromValue()} dx="30" dy="0.3em">
					wee!
				</text>

				<CircularThumb />
			</>
		)}
	</CircularInput>
)

It might be a good time to dive deep into customisation with components and hooks: