Advertisement

Neumorphic Drag Dial Thermostat

| by Vladimir | 3 min read | code by Jon Kantner
Intermediate

Tech & Dependencies

HTML CSS JavaScript
GSAP Draggable

Features

  • Neumorphism
  • Draggable Dial
  • Dynamic Gradients
  • Keyboard Accessible

Browser Support

Chrome 60+ Safari 11+ Firefox 55+ Edge 79+

Core

This is a Neumorphic Drag Dial Thermostat. It provides a tactile, skeuomorphic interface for setting numerical values (like temperature) through rotational dragging or keyboard input. The dial dynamically reveals cool-to-warm gradient colors based on the current value, enhancing the physical “feel” of a smart home control panel.

Specs

  • Weight: ~130 KB (due to GSAP and Draggable dependencies).
  • Performance: High. Rotation calculations are handled by GSAP, while the color transitions and scaling effects are offloaded to CSS opacity and transforms.
  • Theming / Customization: Easily customizable via CSS variables (--l1 through --l6 for the neumorphic shadows and highlights). Includes native light/dark mode support via prefers-color-scheme.
  • Responsiveness: Uses viewport-relative font sizing (calc(16px + ...)) so the entire UI scales fluidly on different devices.
  • Graceful Degradation: Without JavaScript, it remains a static UI element. With JS but without GSAP, the keyboard controls still function, but the dragging interaction breaks.

Anatomy

The structure mimics a physical control knob set into a soft, extruded surface.

  • HTML (The Skeleton): The .temp container holds the main .temp__dial and a .temp__outdoors info panel. The dial consists of overlapping layers: .temp__drag (the interaction area), .temp__dial-shades (the color gradients), .temp__dial-core (the inner shadow creating the “trench”), and the text value.
  • CSS (The Skin): Relies heavily on box-shadow (combining inset and outset shadows with opposing light/dark colors) to create the soft, raised/depressed neumorphic look. Dynamic opacity swaps the cool and warm radial gradients.
  • JS (The Nervous System): An ES6 Class (NeuThermostat) initializes GSAP’s Draggable on the knob. It translates the rotational angle into a temperature value (and vice-versa), updates CSS variables (--angle), and handles keyboard accessibility (Up/Right to increase, Down/Left to decrease).

Logic

The component intelligently extracts the rotation angle applied by GSAP and translates it back into a bounded temperature scale.

angleFromMatrix(transVal) {
	let matrixVal = transVal.split('(')[1].split(')')[0].split(','),
		[cos1,sin] = matrixVal.slice(0,2),
		angle = Math.round(Math.atan2(sin,cos1) * (180 / Math.PI)) * -1;
	
	// convert negative angles to positive
	if (angle < 0) angle += 360;
	if (angle > 0) angle = 360 - angle;
	
	return angle;
}

When dragging, GSAP applies a transform: matrix(...) to the element. Instead of keeping a separate state variable for the angle, this function parses the raw CSS matrix string, uses Math.atan2 on the sine and cosine values, and converts the result back into standard degrees (0-360). This angle is then mapped against the defined minimum (15deg) and maximum (345deg) rotation bounds to calculate the exact temperature integer.

Feel

Soft, responsive, and highly tactile. The neumorphic design gives the dial a satisfying visual weight. When the user interacts (via drag or keypress), the inner core physically “depresses” into the screen via an animated drop shadow, while the outer ring simultaneously glows with a blue (cold) or orange (hot) gradient depending on the temperature setting. The numbers flip like a mechanical odometer, completing the sensory illusion of operating a high-end physical device.

Advertisement