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
house reaches 1 and 23 verticallysearch spans 1–23 both ways- Checked. The canvas is
24×24withviewBox="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
2pxstroke,stroke="currentColor",stroke-linecap="round", andstroke-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 otherfillis 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
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:optimizesnaps 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:optimizerounds it.
Keyline shapes
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
squareserverlayout-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:optimizesmooth 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:optimizeapplies it to every circular arc that meets two straight lines tangentially, using the construction inscripts/corner-smoothing.mjs, andbun run icons:checkrejects 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-offhas 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
layout-gridservercircle-alertSeparate 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, andtrain-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
circlesquarebellhouseCompare a new icon with circle and square at 24px. Its ink should look about as dark as theirs.
circlecovers 21.7% of the canvas with ink andsquare24.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, andtable-columns-splitare around 44–46%) are as dense as an icon should get. Simplify before going further. - Reuse base shapes exactly.
square-plusdraws thesquareframe unchanged, and so do 49 of the 78square-*icons. 33 of the 48circle-*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
lucideVersioninmetadata/lucide.json(currently 1.61.0). Deprecated aliases are rejected: usecircle-alert, notalert-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.svgbecomesCircleAlert. 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 exactlyxmlns="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", andstroke-linejoin="round". Any other root attribute, such asclass,id, orstyle, 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:
don a path;cx,cy, andron a circle;x1,y1,x2, andy2on a line;pointson a polyline or polygon;x,y,width, andheighton a rect, sincerxandrycan’t carry corner smoothing; andcx,cy,rx, andryon an ellipse. Nofill,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
fillother thannoneis 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:optimizeThis 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
Mcommands, and stubs shorter than 0.05px hidden under another stroke, are removed. Dots drawn ash.01are 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:checkThis 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.mjsSee Contributing for setup, tests, and pull requests.