Browse documentation

Robot rover

A ground robot in plan view. Choose four or six wheels, steer the front axle, and drive its heading and tread travel from your application.

ROVER / 06
variant
wheels
heading
25°
steer
15°
travel
0.00

Install

pnpm dlx shadcn@latest add https://robocn.dev/r/robot-rover.json

Notes

  • All positions are controlled and update immediately. The component has no timer; connect your own timeline or telemetry for motion.
  • This is a pose illustration, not a vehicle dynamics solver. Steering rotates the front wheels equally and does not integrate a driving path.

Usage

import { RobotRover } from "@/components/ui/robot-rover"

<RobotRover wheels={6} heading={25} steering={15} wheelTravel={0.4} active />

Props

PropTypeDefaultDescription
wheels4 | 64Two or three axles. Only the front axle steers.
headingnumber0Clockwise degrees from the top. Wraps after each turn; non-finite values use zero.
steeringnumber0Front-wheel angle, clamped to −45..45 degrees. Non-finite values use zero.
wheelTravelnumber0Controlled tread phase. Whole turns repeat; decrease to reverse. Non-finite values use zero.
showSensorbooleantrueDraw the roof-mounted sensor turret.
activebooleanfalseIlluminate the front status lights.
labelstringCaption underneath the rover, independent of heading.
variant"solid" | "outline" | "blueprint" | "wire""solid"How the machine is painted. Geometry never changes between variants.
size"xs" | "sm" | "md" | "lg" | "xl" | number"md"Rendered width in pixels, or a step on the scale.
colorstringvar(--robot-shell)Body panels — the colour the machine reads as.
accentstringvar(--robot-accent)Status colour: tip light, live tool, readouts.
metalstringvar(--robot-metal)Bare machined parts: collars, bolts, tool bodies.
darkstringvar(--robot-dark)Cast joints, base, shadow side.
palettePartial<RobotPalette>Override any subset of roles at once, including glow and grid.

Source

src/components/ui/robot-rover.tsx
"use client"

import * as React from "react"

import {
  px,
  resolveRobotPalette,
  resolveRobotSize,
  robotSurface,
  type RobotPaletteProps,
  type RobotSize,
  type RobotVariant,
} from "@/lib/robocn/style"
import { cn } from "@/lib/utils"

export interface RobotRoverProps
  extends Omit<React.ComponentProps<"svg">, "color">, RobotPaletteProps {
  /** Clockwise degrees from the top of the drawing. */
  heading?: number
  /** Front-wheel steering in degrees, clamped to -45..45. */
  steering?: number
  wheels?: 4 | 6
  /** Controlled tread travel. Whole turns produce the same drawing. */
  wheelTravel?: number
  showSensor?: boolean
  active?: boolean
  label?: string
  size?: RobotSize | number
  variant?: RobotVariant
}

function RobotRover({
  heading = 0, steering = 0, wheels = 4, wheelTravel = 0,
  showSensor = true, active = false, label, size = "md", variant = "solid",
  color, accent, metal, dark, glow, grid, palette: paletteOverride,
  className, style, ...props
}: RobotRoverProps) {
  const palette = resolveRobotPalette({ color, accent, metal, dark, glow, grid, palette: paletteOverride })
  const width = resolveRobotSize(size)
  const angle = Number.isFinite(heading) ? ((heading % 360) + 360) % 360 : 0
  const steer = Number.isFinite(steering) ? Math.max(-45, Math.min(45, steering)) : 0
  const travel = Number.isFinite(wheelTravel) ? ((wheelTravel % 1) + 1) % 1 : 0
  const axles = wheels === 6 ? [-32, 0, 32] : [-32, 32]
  const wheelClip = `rover-wheel-${React.useId().replace(/:/g, "")}`
  const shell = robotSurface("shell", variant, palette)
  const machined = robotSurface("metal", variant, palette)
  const cast = robotSurface("dark", variant, palette)

  return (
    <svg role="img" aria-label={`${axles.length * 2}-wheel robot rover, heading ${Math.round(angle)} degrees`}
      viewBox="0 0 180 180" width={width} height={width}
      className={cn("max-w-full select-none", className)} style={{ color: palette.foreground, ...style }} {...props}>
      <defs><clipPath id={wheelClip}><rect x={-8} y={-16} width={16} height={32} rx={4} /></clipPath></defs>
      {variant === "blueprint" && <g stroke={palette.grid} strokeWidth={0.5} opacity={0.5}>
        <circle cx={90} cy={90} r={72} fill="none" strokeDasharray="2 3" />
        <path d="M 90 12 V 168 M 12 90 H 168" strokeDasharray="2 3" />
      </g>}
      <g data-chassis transform={`translate(90 90) rotate(${px(angle)})`}>
        {axles.map((y, axle) => <g key={y}>
          <rect x={-43} y={y - 3} width={86} height={6} rx={2} {...machined} />
          {[-1, 1].map(side => <g key={side} data-wheel={`${axle}-${side}`}
            data-steering={axle === 0 ? steer : 0}
            transform={`translate(${side * 43} ${y}) rotate(${axle === 0 ? px(steer) : 0})`}>
            <rect x={-8} y={-16} width={16} height={32} rx={4} {...cast} />
            <g clipPath={`url(#${wheelClip})`}>
              <g data-tread transform={`translate(0 ${px(travel * 6)})`} stroke={palette.metal} strokeWidth={1} opacity={0.65}>
                {Array.from({ length: 7 }, (_, i) => <path key={i} d={`M -6 ${i * 6 - 21} h 12`} />)}
              </g>
            </g>
          </g>)}
        </g>)}
        <rect x={-30} y={-48} width={60} height={96} rx={12} {...shell} />
        <rect x={-23} y={-40} width={46} height={13} rx={4} {...cast} />
        {[-17, 17].map(x => <rect key={x} x={x - 4} y={-37} width={8} height={4} rx={1} fill={active ? palette.accent : palette.metal} />)}
        <rect x={-22} y={-15} width={44} height={42} rx={5} {...machined} />
        {[-13, -6, 1, 8].map(y => <path key={y} d={`M -13 ${y + 10} h 26`} stroke={palette.dark} strokeWidth={2} />)}
        {showSensor && <g>
          <circle cy={-11} r={13} {...cast} />
          <circle cy={-11} r={9} {...shell} />
          <circle cy={-11} r={4} fill={palette.accent} />
          <path d="M 0 -13 v -7" stroke={palette.dark} strokeWidth={1.5} />
        </g>}
        <path d="M -7 -22 L 0 -27 L 7 -22" fill="none" stroke={palette.accent} strokeWidth={1.5} />
        <rect x={-21} y={37} width={42} height={5} rx={2} {...cast} />
        {[-24, 24].flatMap(x => [-23, 31].map(y => <circle key={`${x}-${y}`} cx={x} cy={y} r={1.5} fill={palette.dark} />))}
      </g>
      {label && <text x={90} y={176} textAnchor="middle" fontSize={5} fontFamily="ui-monospace, monospace" fill={palette.foreground}>{label}</text>}
    </svg>
  )
}

export { RobotRover }