@web-portfolio/icons-sanity
v3.0.1
Published
Sanity Studio icon picker input, backed by the same bundled registry as @web-portfolio/icons.
Maintainers
Readme
@web-portfolio/icons-sanity
A Sanity Studio plugin that adds a proper icon
picker field to your schema, backed by the same 633-icon library as
@web-portfolio/icons.
It exists because "paste an SVG into a text field" is a bad experience for
content editors — they either need to know what valid SVG markup looks
like, or they need you to do it for them every time a new skill or social
link gets added. This plugin turns that into: click the field, search,
pick an icon, done.
Browse the icon set → before you install, to see exactly what your editors will be choosing from.
Recent changes
v3.0.0 — ESM-only, built with @sanity/plugin-kit
- The build now uses
@sanity/plugin-kitand@sanity/pkg-utilsinstead of a hand-rolledtsupconfig — the same toolchain Sanity's own plugin ecosystem uses. - Breaking: the package is now ESM-only. The
require()/CJS entry point (main,modulefields, and therequireexport condition) has been removed — Sanity Studio v3+ is pure ESM, so this plugin was never actually loaded viarequire()in practice. If you import this package withrequire()directly (outside a Studio config), switch toimport. - No other behavior changed: same
sanityIconPicker()export, sameiconRefschema type, same peer dependencies.
97% smaller package
- This plugin no longer bundles its own copy of the 633-icon SVG registry.
It now renders icon previews through
@web-portfolio/icons's<Icon>component (added as a peer dependency, see below) and only carries the lightweight search index — name, label, tags, category, no SVG markup — that the picker's search and filtering actually need. - Published size: 1.1 MB → 35.7 KB (tarball), 1.36 MB → 60.5 KB
(unpacked JS), 577 KB → 15 KB (gzipped). Since any project using this
plugin already installs
@web-portfolio/iconsto render the picked icon on its frontend, that icon data is no longer downloaded twice.
Security & supply chain
- Bumped
vitestto 3.2.7 anddrizzle-ormto 0.45.2 across the monorepo, and pinned several transitive dependencies (esbuild,sharp,postcss,glob,js-yaml,uuid,prismjs,adm-zip,es-define-property) to patched versions via pnpm overrides. - Install scripts now run only for an explicit allow-list (
esbuild,sharp) instead of the whole dependency tree by default. - Current
Socket.dev
score: 100 Vulnerability, 100 Quality, 100 License, 88
Maintenance, 80 Supply Chain Security. This plugin still ships zero
runtime dependencies of its own — the Supply Chain number reflects
Socket's scan of the full Sanity Studio peer tree (
sanity,@sanity/ui, ...), not this plugin's code.
Why this plugin
If you've built a portfolio, agency site, or any Sanity project with a "skills" or "tech stack" or "connect with us" section, you've probably built some version of an icon field before. This plugin is meant to save you from doing that again:
- 633 icons, ready to search. Tech-stack logos, social platform icons, and everyday UI icons — editors search by name, label, or tag, and can narrow to a category, no SVG knowledge required.
- Results ranked, not just filtered. Typing
goleads withgo, notgodot— exact matches first, then name prefixes, then everything that merely mentions the term. - Nothing fetched while editing. Icon markup (via
@web-portfolio/icons) and search metadata (via this plugin) are both bundled at build time, so the picker opens instantly and works even if Studio is running offline. - Stores a name, not a blob. The field value is a plain string
(
"react","github") rather than raw markup or an asset reference, which pairs directly with@web-portfolio/icons's<Icon name="..." />on your frontend — no lookups, no asset resolution, just the name. - Shows what's selected. The current icon renders inline in the form alongside its label, category, and the exact string that got stored — so editors can confirm at a glance they picked the right one.
- Built entirely from Sanity UI.
Card,Dialog,Badge,Select,TextInput,Tooltipand@sanity/icons, sized off the Sanity UI space scale. It inherits your Studio's theme and light/dark scheme instead of fighting it, and respectsreadOnlyfields and Studio's focus/presence tracking like a built-in input.
Installation
npm install @web-portfolio/icons-sanitypnpm add @web-portfolio/icons-sanityPeer dependencies:
sanity >=3, @sanity/ui >=2, @sanity/icons >=3, react >=18,
styled-components >=6 (already present in any standard Sanity Studio v3
project), plus @web-portfolio/icons >=1.0.1, which renders the icons this
plugin picks — install it if your Studio doesn't already have it.
Setup
Add the plugin to your Studio config:
// sanity.config.ts
import { defineConfig } from 'sanity'
import { sanityIconPicker } from '@web-portfolio/icons-sanity'
export default defineConfig({
// ...your existing config
plugins: [
// ...your existing plugins
sanityIconPicker(),
],
})This registers an iconRef schema type you can use on any document or
object.
Usage
Use type: 'iconRef' on any field that should store an icon:
// schemaTypes/documents/skill.ts
import { defineField, defineType } from 'sanity'
export default defineType({
name: 'skill',
title: 'Skill',
type: 'document',
fields: [
defineField({ name: 'name', type: 'string' }),
defineField({ name: 'icon', title: 'Icon', type: 'iconRef' }),
],
})In Studio, that renders as a field editors click to open a searchable grid.
They type "react" or "docker" or "mail", pick a result, and the field
stores that icon's name as a plain string — e.g. "react".
What editors see
- The collapsed field shows the chosen icon, its label, its source category, and the raw string stored in the document.
- Clicking Select icon / Change icon opens a dialog with a search field and a category dropdown (Devicon, Material, Social, Tools, …). Results render a page at a time so the dialog opens instantly even though the whole 633-icon set is in memory.
- Hovering a tile reveals its registry name — the exact value that gets saved.
- A
readOnlyfield (schema-level, or a role without write access) disables both actions and emits no patches. - If a document holds a name that is no longer in the bundled set — say the icon was renamed upstream — the field says so explicitly instead of rendering an empty box, and offers to replace or clear it.
Building your own input
If iconRef isn't the shape you want, the pieces are exported so you can
wire the same picker into a custom schema type or reuse its matching logic:
import {
IconPickerInput, // the input component itself
formatCategoryLabel, // "original" -> "Devicon"
matchesQuery, // name/label/tag matching
rankMatch, // relevance score, lower is better
type IconEntry, // the shape matchesQuery/rankMatch expect
} from '@web-portfolio/icons-sanity'Social/contact links example
defineField({
name: 'socialLinks',
title: 'Social links',
type: 'array',
of: [
defineType({
type: 'object',
name: 'socialLink',
fields: [
defineField({ name: 'icon', type: 'iconRef' }),
defineField({ name: 'url', type: 'url' }),
],
}),
],
})Rendering the picked icon on your frontend
This is the part that makes the whole thing worth it — pair with
@web-portfolio/icons
and pass the stored string straight through as name, no transformation
needed:
import { Icon } from '@web-portfolio/icons'
function SkillBadge({ skill }: { skill: { name: string; icon: string } }) {
return (
<span>
<Icon name={skill.icon} size={20} />
{skill.name}
</span>
)
}What's in the icon set
633 icons from three sources, shared with @web-portfolio/icons:
- devicon (578 icons, MIT) — programming languages, frameworks, databases, cloud platforms, dev tools.
- Material Symbols (38 icons, Apache-2.0) — everyday UI icons: mail, call, arrows, menu, close, connected TV, bolt, translate, checklist, gift, and more.
- Simple Icons (17 icons, CC0-1.0) — social platforms and dev-tool brands devicon doesn't cover (Instagram, YouTube, WhatsApp, MCP, LangChain, Ollama, and more).
Not sure exactly what's in there? The gallery is the fastest way to check — search or filter by category to see every icon before you commit to using it in a schema.
License
MIT — see LICENSE. Bundled icon sets keep their original licenses (devicon: MIT, Material Symbols: Apache-2.0, Simple Icons: CC0-1.0) — see the repo README for full attribution.
