Skip to content
Esc
↑↓navigate↵open⌘Jpreview
On this page

Design guide

The canvas, stroke, pixel grid, keyline shapes, corner radii and smoothing, spacing, and naming rules every Vadivam icon follows, measured from the full set.

These rules describe how the Vadivam icons are drawn. The numbers come from measuring every icon in icons/ with bun scripts/icon-geometry.mjs. Where a rule is not met by every icon, the share that meets it is given. Rules marked Checked fail bun run icons:check; the rest are reviewed by eye.

Principles

  • One grid. Every icon is drawn on the same 24×24 canvas with the same 2px stroke, so icons line up and match in weight when they sit next to each other.
  • Crisp at 24px. Straight edges land on whole pixels at 1×, so the stroke renders as solid pixels rather than a blur.
  • Soft, generous shapes. Base shapes fill the canvas and use large corner radii with 60% corner smoothing, the same curve as iOS. Vadivam’s square is 20×20 with a smoothed 6px radius, where Lucide’s is 18×18 with a circular 2px.
  • Lucide’s vocabulary, redrawn. Every icon is a new drawing of a Lucide icon under the same name, so a Lucide name works in Vadivam unchanged.
  • Clarity over rules. When a rule hurts how readable an icon is, readability wins. The exceptions below are listed by name.

Canvas and live area

24×24 canvas, 1px padding, centerlines inside 2–22
house reaches 1 and 23 vertically
search spans 1–23 both ways
  • Checked. The canvas is 24×24 with viewBox="0 0 24 24".
  • Checked. Keep 1px of padding. With round caps and joins, a 2px stroke reaches 1px past its centerline in every direction, so ink stays inside 1–23 and centerlines stay inside 2–22. All 1,903 icons pass.
  • Use the live area. 82.4% of icons reach the padding line on at least one side, and the median icon’s ink is 22px across its larger dimension. Only 11.8% stay inside 2–22.
  • Center the drawing. In 99.1% of icons, the center of the ink’s bounding box is within 0.5px of (12, 12). Asymmetric icons may shift for optical balance.

Stroke

  • Checked. Draw with a single 2px stroke, stroke="currentColor", stroke-linecap="round", and stroke-linejoin="round", all set once on the root <svg>.
  • Keep the stroke centered on the path, which is how SVG renders it. Draw open centerlines and never outline a stroke into a filled shape.
  • Checked. A path with more than 8 closed subpaths is rejected, since that usually means outlined stroke geometry. The most in any icon today is 3.
  • Checked. No fills: the root sets fill="none", and source artwork with any other fill is rejected before it is optimized.
  • Do not mix stroke widths or emulate a thicker line with parallel strokes. Consumers change the weight with strokeWidth; see Usage.

Pixel grid alignment

Centerline on y = 7: two solid pixel rows
Centerline on y = 7.5: three soft rows

A 2px stroke centered on a whole-number coordinate covers exactly two pixel rows at 24px, so it renders solid. Centered on a half pixel, the same stroke covers one full row and two half rows, and renders blurred. So, for crisp edges:

  • Put horizontal and vertical lines on whole-pixel coordinates. 93.6% of the set’s 6,933 horizontal and vertical straight segments do (94.4% of those at least 2px long). In 87.7% of icons, every one does.
  • Checked. A horizontal or vertical line at least 1px long that sits less than 0.1px off a whole pixel is an export slip, not a choice. bun run icons:optimize snaps it onto the whole pixel, along with every point that shares its coordinate, so strokes meeting it move with it. Lines 0.1px or more off, such as those on half pixels, are left as drawn.
  • Start rectangles on whole pixels: 96.4% of rectangle corners do.
  • Center circles on whole pixels where you can (75.6%), or on half pixels (13.6%) when an odd-sized detail needs it.
  • Let curves and diagonals follow the shape. Only 44.4% of all vertices are on whole pixels, because arc and curve endpoints land wherever the shape puts them, and smoothed corners start part-way along each edge. Of 3,120 diagonal segments, 39% run at 45°.
  • Path data keeps at most 3 decimal places. bun run icons:optimize rounds it.

Keyline shapes

Circle: r 10 at (12, 12)
Square: 20×20, r 6
Portrait: 14×20, r 4
Landscape: 20×14, r 4

Start from these base shapes. Each is drawn as its own icon, so it can be checked against the set:

Shape Geometry Defined by Measured use
Circle r 10 at (12, 12) circle 77 full circles, all centered on (12, 12)
Square 20×20 at (2, 2), r 6 square 82 rectangles at 20×20, 80 of them with r 6
Portrait 14×20 at (5, 2), r 4 rectangle-vertical 13 rectangles at 14×20, at r 4 or r 3
Landscape 20×14 at (2, 5), r 4 rectangle-horizontal 16 rectangles at 20×14, all with r 4 (the monitor family)

Landscape frames usually need more height than 14px. The common alternatives are 20×18 at (2, 4) with r 5.5, used 8 times (the calendar family), and 16×12 at (2, 6) with r 3.75, used 8 times. Both radii are the largest that leave room for full corner smoothing on the shorter side.

Small circles use radius 2 (79 circles), 3 (56), 1 (35), 3.5 (31), or 2.5 (30).

Corner radius

20px side, r 6 in square
14px side, r 4 in server
8px side, r 2.5 in layout-grid
  • Round every rectangle. None of the 338 axis-aligned rectangles in the set has a sharp corner.
  • Make the radius about 30% of the rectangle’s shorter side, on a 0.5px step where you can. The median ratio is 0.30; 80% of rectangles fall between 0.29 and 0.50.
  • For sides of 8px or more, the common radii are 6 (84 rectangles), 2.5 (49), 4 (37), and 3 (18).
  • For sides under 8px, the common radii are 2 (40), 1.5 (18), and 2.5 (18). A 4px-wide bar becomes a pill with r 2.
  • Draw corners as circular arcs, then let bun run icons:optimize smooth them. Don’t draw the smoothing by hand. The set has 6,858 circular arc commands with a median radius of 3.5.

Corner smoothing

  • Checked. Smooth every rounded corner at 60%, the same curve as iOS and Figma’s iOS preset. A smoothed corner leaves the straight edge on a cubic curve, turns through a shorter arc of the same radius, and eases back onto the next edge, so the bend has no sudden start. bun run icons:optimize applies it to every circular arc that meets two straight lines tangentially, using the construction in scripts/corner-smoothing.mjs, and bun run icons:check rejects any such corner left as a plain arc. 2,880 of the set’s 2,881 such corners are smoothed; the last is below.
  • Leave room for it. A 60% corner reaches 1.6 times its radius along each edge, so it fits when the radius is at most the edge ÷ 3.2 (6.25 on a 20px side, 2.5 on an 8px side). Where it doesn’t fit on an edge of 8px or more, optimize tightens the radius to the largest 0.25px step that does: a 20×18 frame gets r 5.5, and an 8×8 tile gets r 2.5. 2,189 corners (76.0%) are at the full 60%.
  • On edges under 8px, and at corners sharper than 90°, the radius is kept and the corner gets as much smoothing as the edge allows, as Figma does. Tightening a sharp corner would push its tip outward. These 691 corners have a median smoothing of 33%. A corner with room for less than 5% smoothing keeps its plain arc, since that little is invisible: the r 7 corner in webcam-off has only 0.05px of straight edge on each side.
  • Some arcs are not corners and stay circular: bends that turn less than 30°, arcs with no straight line on one side (pill ends, curved stroke ends), and corners where another stroke attaches at the end of the arc, such as the pins on some calendar-* icons and folded file corners.

Lucide uses circular 2px corners on shapes 8px and up and 1px below. Vadivam’s larger, smoothed radii are what give it its softer look, so don’t copy Lucide’s corners.

Spacing between elements

2px gap in layout-grid
2px gap in server
1.5px gap in circle-alert

Separate parts of an icon either join or leave a readable gap:

  • Leave a 2px gap between separate elements by default. In the set, 68.7% of separate parts that come within 6px of each other keep at least 2px; 2px is the most common gap (1,320 pairs).
  • Never go below 1.5px. 1.5px is the second most common gap (1,310 pairs), used where 2px doesn’t fit, such as the dot under a bar. In 86.9% of icons with separate parts, every gap is at least 1.5px.
  • Don’t leave a near miss. If two strokes come closer than 1.5px, join them. 63 icons, including image, earth, and train-front-tunnel, have a stroke ending 0.3–2px short of another, so the strokes merge into a blob without meeting.

These figures measure the gap between separate subpaths: their centerline distance minus the 2px stroke. Gaps within a single subpath, such as between the turns of a spiral, are not measured.

Visual weight and consistency

21.7% ink in circle
24.3% ink in square
23.1% ink in bell
29.5% ink in house

Compare a new icon with circle and square at 24px. Its ink should look about as dark as theirs.

  • circle covers 21.7% of the canvas with ink and square 24.3%. Across the set, the median is 26.3%, and 90% of icons fall between 15.5% and 36.2%.
  • Icons above about 36% (grid-3x3, fence, and table-columns-split are around 44–46%) are as dense as an icon should get. Simplify before going further.
  • Reuse base shapes exactly. square-plus draws the square frame unchanged, and so do 49 of the 78 square-* icons. 33 of the 48 circle-* icons contain the full r 10 circle. The rest break the frame for dashes or to make room for a badge, but keep its geometry.
  • Put modifiers, such as a plus or a slash, in the same place and at the same size across a family.
  • Keep icons simple. The median icon is 1 <path> with 3 subpaths and 13 segments. 1,347 icons use 1 path, 532 use 2, 23 use 3, and 1 uses 4. The most complex has 14 subpaths.

Naming

  • Checked. Every file name is a current canonical Lucide name from the pinned release, recorded as lucideVersion in metadata/lucide.json (currently 1.61.0). Deprecated aliases are rejected: use circle-alert, not alert-circle.
  • Checked. Names are lowercase kebab-case: letters and digits separated by single hyphens.
  • Checked. Every icon has tags and at least one known category in metadata/lucide.json, taken from Lucide’s metadata for the same name.
  • Component names are the PascalCase form of the file name, generated by the build: circle-alert.svg becomes CircleAlert. Never name a component by hand.

What’s not allowed

bun run icons:check and bun run icons:optimize reject an SVG that breaks any of these rules:

  • Root. The root <svg> has exactly xmlns="http://www.w3.org/2000/svg", width="24", height="24", viewBox="0 0 24 24", fill="none", stroke="currentColor", stroke-width="2", stroke-linecap="round", and stroke-linejoin="round". Any other root attribute, such as class, id, or style, is rejected.
  • Elements. Only <path>, <circle>, <line>, <polyline>, <polygon>, <rect>, and <ellipse> are allowed. That rules out <a>, <clipPath>, <defs>, <filter>, <foreignObject>, <g>, <image>, <linearGradient>, <mask>, <pattern>, <radialGradient>, <script>, <style>, <symbol>, <text>, <use>, and any other element. Every icon in the set uses only <path>.
  • Attributes. Each element carries geometry only: d on a path; cx, cy, and r on a circle; x1, y1, x2, and y2 on a line; points on a polyline or polygon; x, y, width, and height on a rect, since rx and ry can’t carry corner smoothing; and cx, cy, rx, and ry on an ellipse. No fill, stroke, transform, opacity, style, class, id, or event handlers.
  • Geometry. Every element needs geometry. An icon needs at least one drawable element. Path data must parse. A single path may hold at most 8 closed subpaths. The stroke must stay inside the 1px padding. Every rounded corner between two straight lines must be smoothed. Path data must already be clean: no strokes split where they meet, no stray subpaths, no straight lines in pieces, and no lines just off the pixel grid.
  • Fills. Source artwork with a fill other than none is rejected before optimization, so outlined Figma exports never reach the set.
  • Files. File names must be canonical Lucide names in lowercase kebab-case, with an entry in metadata/lucide.json.

Treat every SVG as untrusted input: these checks run before anything is published or deployed.

Accessibility

An icon is either decorative or meaningful. It is decorative when it repeats nearby text, such as an icon next to a button label. It is meaningful when it carries information on its own, such as an icon-only button or a status indicator.

  • Hide decorative icons from assistive technology. The web framework components add aria-hidden="true" when no title or accessible attribute is given.
  • Give a meaningful icon a name with title, aria-label, or, in React Native, accessibilityLabel. Name an icon-only button on the button itself.
  • Don’t rely on an icon’s shape or color alone to carry state.

See Usage: accessibility for examples in each framework.

Workflow

Draw in the Figma source file

Each icon is a top-level 24×24 frame named after its Lucide name, on a 2px grid. Draw centerline strokes on the frame. Don’t flatten or outline them. The internal Figma plugin (apps/vadivam-internal) creates frames for missing names and replaces artwork from SVG files. Its Audit tab checks every frame for a canonical name, 24×24 size, no background fill or layout guides, stroke-only artwork, zero corner radius, a 2px stroke, round caps and joins, and zero rotation. Keep corners as circular arcs in Figma; icons:optimize adds the smoothing. After optimizing, bring the smoothed artwork back with the plugin’s Replace tab, and run Update library in the library file, so Figma matches icons/.

Export to icons/

Export the frame as SVG into the root icons/ folder, replacing the existing file of the same name. Generated package and website files are rebuilt from this folder; never edit them by hand.

Normalize

bun run icons:optimize

This optimizes each SVG in place with SVGO, cleans the path data, smooths every rounded corner at 60%, rewrites the root attributes, and strips stroke and fill attributes from the elements. Running it again changes nothing.

Cleaning the path data undoes what Figma’s export leaves behind, without changing how the icon looks:

  • Strokes that end at the same point are joined into one, preferring the stroke that continues straight on at a junction. With round caps and joins, two strokes meeting at a point render exactly like one stroke turning there, and a joined outline lets smoothing see both sides of each corner.

  • Lone M commands, and stubs shorter than 0.05px hidden under another stroke, are removed. Dots drawn as h.01 are kept.

  • A straight line split into pieces becomes one line.

  • Horizontal and vertical lines just off the pixel grid are snapped onto it, as described under Pixel grid alignment.

  • < title="Validate">
bun run icons:check

This runs every Checked rule on this page and confirms the names against the pinned Lucide release.

Review by eye

Compare the icon with circle, square, and its family at 24px and at 1×, against the rules that are not checked automatically: grid alignment, keylines, corner radius, gaps, and weight. To see where the icon sits in the set’s distributions, run:

bun scripts/icon-geometry.mjs

See Contributing for setup, tests, and pull requests.

Last updated on

Was this page helpful?