Pitvolt Brand pitvolt.com โ†’
04 - Iconography

A small twist, applied everywhere.

Phosphor Light is the geometric backbone. The Pitvolt twist is two marks: a clay-red corner notch on the top-right, and a yellow voltage spark at the lower-left. Both apply via CSS, so the icon family scales without per-icon redrawing.

Base family
Phosphor Light
Geometric skeleton, ~1.5 stroke. Pairs with Bricolage / Pally architecture.
Twist
Notch + spark
Clay-red corner cut + yellow voltage dot. Applied via wrapper, not per-icon.
Delivery
<PvIcon name="plug" />
One component, one prop. react-icons dependency only.

Anatomy

What every icon is composed of, top to bottom.

  • Glyph
    Phosphor Light. Inherits currentColor from text. Stroke 1.25.
  • Notch - top right
    22% triangle cut from the corner, filled with --accent-secondary.
  • Spark - lower left
    14% dot, --accent-primary with a soft glow. The voltage signal.

Sizes

Notch and spark scale up at small sizes so the brand reads at 14px. They scale down at large sizes so the glyph stays the hero.

The library

First pass: 26 icons, grouped by use. Add to PvIcon.jsx's registry to extend.

Variants

Outline is default. Filled for active/selected. Tones for status. plain drops the twist when an icon needs to disappear into UI.

In context

How they actually look inside the page patterns you've already built.

Nav
Feature list (on off-white)
Status row
Inline with copy

Reserve a stall at one of our partner sites - most go live within 2 weeks of contract signing.

Have a question? Email Joojo

Usage

One component. One prop. Twist is automatic.

// 1. Install
npm i react-icons

// 2. Import once
import './icons.css';
import { PvIcon } from './PvIcon';

// 3. Use
<PvIcon name="plug" />
<PvIcon name="lightning" size="lg" />
<PvIcon name="warning" tone="warning" title="Charger offline" />
<PvIcon name="check" filled />
<PvIcon name="external-link" plain />  // no twist

The component

PvIcon.jsx in full. It lives in the repo at brand/components/PvIcon.jsx; copy it alongside icons.css.

Show PvIcon.jsx (138 lines) โ†’
// =============================================================
// Pitvolt - <PvIcon> React wrapper
//
// Single component. Single prop. Applies the brand "twist" (clay-red
// corner notch + yellow voltage spark) to any react-icons glyph.
//
// Usage:
//   import { PvIcon } from './PvIcon';
//   <PvIcon name="plug" />
//   <PvIcon name="lightning" size="lg" />
//   <PvIcon name="warning" tone="warning" />
//   <PvIcon name="check" filled />
//   <PvIcon name="external-link" plain />     // no twist
//
// Requires:  npm i react-icons
// And:       import './icons.css'  (in your entrypoint)
// =============================================================

import React from 'react';
import {
  // Nav / UI
  PiList, PiX, PiArrowUpRight, PiCaretDown, PiArrowSquareOut, PiMagnifyingGlass,
  // Charging
  PiPlug, PiLightning, PiBatteryHigh, PiGauge, PiChargingStation, PiCar,
  // Host
  PiMapPin, PiBuildings, PiKey, PiCalendarBlank,
  // Status
  PiCheck, PiWarning, PiInfo, PiXCircle,
  // Social
  PiInstagramLogo, PiXLogo, PiLinkedinLogo, PiWhatsappLogo, PiEnvelope,
} from 'react-icons/pi';

// Map friendly names โ†’ react-icons component
const REGISTRY = {
  // Nav / UI
  'menu':           PiList,
  'close':          PiX,
  'arrow':          PiArrowUpRight,
  'chevron-down':   PiCaretDown,
  'external-link':  PiArrowSquareOut,
  'search':         PiMagnifyingGlass,
  // Charging
  'plug':           PiPlug,
  'lightning':      PiLightning,
  'battery':        PiBatteryHigh,
  'gauge':          PiGauge,
  'station':        PiChargingStation,
  'car':            PiCar,
  // Host
  'map-pin':        PiMapPin,
  'building':       PiBuildings,
  'key':            PiKey,
  'calendar':       PiCalendarBlank,
  // Status
  'check':          PiCheck,
  'warning':        PiWarning,
  'info':           PiInfo,
  'x-circle':       PiXCircle,
  // Social
  'instagram':      PiInstagramLogo,
  'x':              PiXLogo,
  'linkedin':       PiLinkedinLogo,
  'whatsapp':       PiWhatsappLogo,
  'mail':           PiEnvelope,
};

export const ICON_NAMES = Object.keys(REGISTRY);

/**
 * @param {Object} props
 * @param {string} props.name      - one of ICON_NAMES
 * @param {'xs'|'sm'|'md'|'lg'|'xl'|number|string} [props.size='md']
 *        Preset key, or a px/css value. Numbers become px.
 * @param {boolean} [props.filled]   - solid glyph instead of outline
 * @param {boolean} [props.plain]    - drop the brand twist (notch + spark)
 * @param {'default'|'warning'|'success'|'info'} [props.tone='default']
 * @param {string} [props.title]     - accessible label; if omitted, icon is aria-hidden
 * @param {string} [props.className] - extra classes on wrapper
 * @param {Object} [props.style]     - extra inline styles on wrapper
 */
export function PvIcon({
  name,
  size = 'md',
  filled = false,
  plain = false,
  tone = 'default',
  title,
  className = '',
  style,
  ...rest
}) {
  const Glyph = REGISTRY[name];
  if (!Glyph) {
    if (typeof console !== 'undefined') {
      console.warn(`[PvIcon] unknown name "${name}". Known names:`, ICON_NAMES);
    }
    return null;
  }

  const sizePresets = ['xs', 'sm', 'md', 'lg', 'xl'];
  const sizeClass = sizePresets.includes(size) ? `pv-icon--${size}` : '';
  const sizeStyle =
    !sizePresets.includes(size)
      ? { '--pv-icon-size': typeof size === 'number' ? `${size}px` : size }
      : null;

  const classes = [
    'pv-icon',
    sizeClass,
    filled && 'pv-icon--filled',
    plain && 'pv-icon--plain',
    tone !== 'default' && `pv-icon--${tone}`,
    className,
  ].filter(Boolean).join(' ');

  const a11y = title
    ? { role: 'img', 'aria-label': title }
    : { 'aria-hidden': true };

  return (
    <span
      className={classes}
      style={{ ...sizeStyle, ...style }}
      {...a11y}
      {...rest}
    >
      <Glyph />
      {!plain && tone !== 'warning' && tone !== 'success' && (
        <span className="pv-icon__notch" aria-hidden="true" />
      )}
      {!plain && (
        <span className="pv-icon__spark" aria-hidden="true" />
      )}
    </span>
  );
}

export default PvIcon;