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 in shadcn/ui
Keyline is listed in shadcn's registry index, so the CLI already knows @keyline. Add icons by name:
npx shadcn add @keyline/bell
npx shadcn add @keyline/fill/bell # any style but stroke is prefixed
npx shadcn add @keyline/sharp/fill/bell # sharp corners lead the path
npx shadcn search @keyline # browse the whole setAny 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.
On an older CLI that does not read the index, add the registry to your components.json yourself:
"registries": {
"@keyline": "https://keylineicons.com/r/{name}.json"
}This 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.
Install in React Native
The same components, drawn through react-native-svg, with the same entry points and export names. It works in Expo, web included.
npm i @keyline-icons/react-native react-native-svgimport { Check } from "@keyline-icons/react-native"
import { Folder } from "@keyline-icons/react-native/duotone"
<Check size={16} color="#2563eb" />
<Folder color={theme.colors.primary} />A native view has no CSS to inherit from, so colour is a color prop rather than currentColor. It defaults to black, and the two-tone and duotone plates follow it.
Vue, Svelte and everything else
The whole set is published on Iconify (opens in a new tab) as keyline-icons, all 11,480 drawings, which is how every framework without a package here gets it. Iconify re-imports from the repository, so it carries whatever the last release drew.
npm i @iconify/vue # Vue 3
npm i @iconify/svelte # Svelte 5import { Icon } from "@iconify/vue" // named
import Icon from "@iconify/svelte" // default
<Icon icon="keyline-icons:bell" />
<Icon icon="keyline-icons:bell-fill" width="16" />
<Icon icon="keyline-icons:bell-sharp-two-tone" />Names carry the style rather than the path doing it, which is Iconify's convention for a set with weights and not a choice made here: stroke is the bare name, and everything else is a suffix on it. -two-tone, -duotone, -fill, -sharp, -sharp-two-tone, -sharp-duotone, -sharp-fill.
Anywhere with no framework package, including Angular, Solid and plain HTML, there is a web component. And for a project with no build step at all, the Tailwind plugin renders each icon as a mask, so it takes text-* like any glyph here:
npm i iconify-icon
<iconify-icon icon="keyline-icons:bell"></iconify-icon>
npm i -D @iconify/tailwind4
@plugin "@iconify/tailwind4";
<span class="icon-[keyline-icons--bell] size-4"></span>Note the doubled hyphen in the class. Tailwind cannot take a colon inside a bracket, so Iconify spells the separator -- there and : everywhere else.
These fetch icon data from Iconify's API on demand rather than bundling it, which is the trade against the React package: nothing ships in your bundle, and a drawing arrives over the network the first time it renders. Every one of them has an offline entry point if that is the wrong trade for you.
Bundle them at build time
To ship the drawings inside your bundle instead, unplugin-icons (opens in a new tab) reads the same Iconify copy at build time and compiles each icon you import into a component for your framework. Only what you import is bundled, and nothing is fetched at runtime.
npm i -D unplugin-icons @iconify-json/keyline-icons// vite.config.ts
import Icons from "unplugin-icons/vite"
plugins: [
Icons({ compiler: "vue3" }), // or "svelte", "solid"
]import IconBell from "~icons/keyline-icons/bell"
import IconBellSharpFill from "~icons/keyline-icons/bell-sharp-fill"
<IconBell class="size-4" />
<IconBellSharpFill />Names take the same suffixes as above. Webpack, Rollup, esbuild and Nuxt have entry points of their own beside /vite. In TypeScript, add unplugin-icons/types/vue or unplugin-icons/types/svelte to compilerOptions.types so the ~icons/ imports resolve.
An icon with no size of its own renders at 1.2em, the plugin's default, so it follows the text around it rather than the 24px the React package draws at. Pass scale: 1 to make that 1em.
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"Every icon also answers to its name plus Icon, CheckIcon beside Check, because that is the form shadcn/ui's own components import. A project from shadcn/create switches with one find-and-replace on the import path. The one name those components use that the set spells differently is Loader2Icon, which is LoaderCircleIcon here.
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.
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.
Four styles, one name
Every icon comes in all four styles. Stroke is the drawing. Two-tone keeps that outline over a 40% plate, which is what duotone meant until 0.9.0. Duotone drops the outline: a grey body with the detail in black. Fill is solid.
Each style is its own entry point in the package, so an app ships only the styles it imports. Code that imported duotone before 1.0.0 and wants the outlined look changes the path to two-tone. The sharp corner treatment is one segment further along, and the export is called the same thing at either path:
import { Bell } from "@keyline-icons/react" // stroke, 1,435
import { Bell } from "@keyline-icons/react/two-tone" // two-tone, 1,435
import { Bell } from "@keyline-icons/react/duotone" // duotone, 1,435
import { Bell } from "@keyline-icons/react/fill" // fill, 1,435
import { Bell } from "@keyline-icons/react/sharp" // sharp stroke, 1,435
import { Bell } from "@keyline-icons/react/sharp/two-tone" // sharp two-tone, 1,435
import { Bell } from "@keyline-icons/react/sharp/duotone" // sharp duotone, 1,435
import { Bell } from "@keyline-icons/react/sharp/fill" // sharp fill, 1,435A name that does not exist 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.
- Does it work in React Native?
- Yes, as @keyline-icons/react-native, installed next to react-native-svg, and in Expo too. The names and entry points are the same as @keyline-icons/react, so shared code changes the package name and nothing else. A native view has no CSS to inherit from, so colour is a color prop rather than currentColor.
- Why does each style have its own import path?
- So an app ships only the styles it imports. Every name is in all four, and the import path picks the look: @keyline-icons/react/two-tone for the outline over a plate, @keyline-icons/react/duotone for the grey body with black detail. Code that imported /duotone before 1.0.0 and wants the old look changes the path to /two-tone.
- 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, two ways. The set is on Iconify as keyline-icons, so @iconify/vue, @iconify/svelte and the iconify-icon web component cover Vue, Svelte, Angular, Solid and plain HTML between them, none of them a package of ours. Or take the files: icons/<style>/<name>.svg in the repository are plain normalised SVGs with no wrapper, no ids and no classes to strip out. Treat those 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.