Install the icons in React or shadcn/ui
The set is drawn on the same 24 grid and the same 2px keyline that shadcn/ui's defaults assume, so it drops in without any adjustment to your components.
Copy a single icon
The fastest path, and it needs no install. Click any icon on the browser and its SVG goes to your clipboard, at whatever size and stroke width you have set there. Paste it straight into a component.
Every drawing takes its colour from currentColor, so it inherits whatever text-* is in scope and needs no fill or stroke attribute of your own.
Install the React package
Every icon is also a React component, generated from the same SVGs, so the two can never disagree.
npm i @keyline-icons/reactimport { Check, Plus, Settings } from "@keyline-icons/react"
<Check className="size-4" />
<Plus size={16} />
<Settings strokeWidth={1.5} />Install with the shadcn CLI
The CLI reads registries from your components.json, so adding the set is one entry:
"registries": {
"@keyline": "https://keylineicons.com/r/{name}.json"
}Any shadcn project already has a components.json, and this needs one: each icon arrives at @components/icons/<name>.tsx, and the alias is what puts it wherever you actually keep components rather than somewhere this set picked. In a project without one, the CLI offers to run init first.
Then add icons by name:
npx shadcn add @keyline/bell
npx shadcn add @keyline/fill/bell # any style but stroke is prefixed
npx shadcn search @keyline # browse the whole setThis is the path that gives you source rather than a dependency. Each icon arrives as a self-contained component that imports nothing from @keyline-icons/react, so you can rename it, edit the drawing, or fold it into whatever conventions your project already has.
Which to choose is a question about ownership rather than about size. Take the package if you want the whole set behind one import and want redraws to arrive with a version bump. Take the registry if you want a handful of icons and would rather own the files than track someone else's releases.
Sizing inside shadcn components
shadcn/ui's primitives size their own icons, and Button does it conditionally. Its base class is [&_svg:not([class*='size-'])]:size-4, which reads: make any nested SVG 16px, unless it already carries a size-* class of its own.
So there are two answers, and which you get depends on how you ask:
<Button>
<Plus /> {/* 16px. The variant sized it. */}
<Plus size={32} /> {/* still 16px: the prop sets a width
attribute, and the class beats it. */}
<Plus className="size-6" /> {/* 24px. The :not() stands down. */}
</Button>The size variants disagree on the number, which is worth checking against your own buttons rather than assuming: xs is 12px, sm is 14px, and the default is 16px. The buttons below are sm, so their icons are 14.
The primitives do not all agree, which is worth knowing before you debug one. DropdownMenu carries the same :not() clause Button does, so your own size-* wins there too. Sidebar does not: it uses [&>svg]:size-4, with no exception and on a direct child rather than any descendant, so a class of your own is ignored there.
Stroke width at small sizes
The set is drawn at 2 units on a 24 grid, which is where it is tested and where it should stay. At 16px that is the weight the drawings were checked at, including the ones that had to be opened up to survive it.
Lighter weights work for large, decorative use. Below 16px they start to break the drawings up rather than refine them, because the gaps between elements were measured against a 2-unit keyline.
Coming from lucide
shadcn/ui ships with lucide, and both sets are 24×24 with a 2px keyline and a currentColor stroke, so the swap is an import change rather than a markup change. This site is drawn entirely with its own icons, which is the same migration done end to end.
- import { Check, Menu } from "lucide-react"
+ import { Check, Menu } from "@keyline-icons/react"Names mostly match, since both follow the same convention. Where they differ, the difference is usually a compound reading base-first: mail-check rather than check-mail. Search the browser for the base word to find the family.
One prop difference worth knowing: lucide's absoluteStrokeWidth has no equivalent here. The stroke scales with the icon, which is what keeps an enlarged keyline looking drawn rather than hairline.
Three styles, one name
Every icon has a stroke drawing. Where the shape encloses an area, or comes in a square or circle container, it also has a duotone and a fill. That is measured off the outline rather than decided by hand, which is why coverage differs between glyphs.
Each style is its own entry point in the package, because they do not cover the same icons and a single component taking a weight would have to accept a combination that does not exist:
import { Bell } from "@keyline-icons/react" // stroke, 585
import { Bell } from "@keyline-icons/react/duotone" // duotone, 480
import { Bell } from "@keyline-icons/react/fill" // fill, 432A name missing from one of them is a build error rather than a blank glyph, which is the better time to find out. Every style is also on disk as plain SVG in the repository (opens in a new tab).
FAQ
The short answers, for scanning. Each one is a section above in longer form.
- Do I need to install anything to use one icon?
- No. The icon browser copies any drawing as SVG or JSX at the size and stroke width you set, and pasting that into a component is the whole install. Every drawing colours from currentColor, so it needs no fill or stroke attribute of your own.
- Which package do I install for React?
- @keyline-icons/react, which generates one component per icon from the same SVGs, so the two cannot disagree. Each style is its own entry point, and the drawings are also in icons/ in the repository at github.com/keyline-icons/keyline-icons if you would rather copy them than install anything.
- Why does each style have its own import path?
- Because the three do not cover the same icons. Stroke has every drawing; duotone and fill only those that enclose an area, so a single component taking a weight prop would accept combinations that do not exist. Importing from @keyline-icons/react/duotone instead makes a missing name a build error rather than a blank glyph.
- How do I size an icon inside a shadcn/ui Button?
- Button sizes nested SVGs itself, with [&_svg:not([class*='size-'])]:size-4, which reads: make any nested icon 16px unless it already carries a size- class. So a bare icon is 16px, a size prop is overridden by that class, and className="size-6" is what actually changes it. Sidebar and DropdownMenu use the same rule without the exception clause, so there your own class will not win.
- What stroke width should I use?
- Two, which is what the set is drawn and tested at on the 24 grid, including at 16px where the tighter drawings had to be opened up to survive. Lighter weights are for large decorative use; below 16px they break the drawings up rather than refine them, because the gaps between elements were measured against a 2-unit keyline.
- How do I switch from lucide?
- Change the import. Both sets are 24×24 with a 2px keyline and a currentColor stroke, so the markup stays as it is. One prop has no equivalent here: lucide's absoluteStrokeWidth. The stroke scales with the icon instead, which is what keeps an enlarged keyline looking drawn rather than hairline.
- Can I use the set without React?
- Yes. icons/<style>/<name>.svg in the repository are plain normalised SVGs with no wrapper, no ids and no classes to strip out, so they drop into any framework or none. Treat them as build output: to change a drawing, change it in raw/ and rebuild.
- Is the set free for commercial use?
- Yes. It is released under the MIT License, which covers commercial and client work. The licence asks that its notice travels with copies of the set itself rather than with a product that happens to use an icon; the LICENSE file in the repository is the actual grant.