npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@grasco/profile-picture

v0.2.0

Published

Lightweight, tree-shakeable profile picture component with ribbon and badge support

Readme

@grasco/profile-picture

A lightweight, framework-agnostic profile picture component built with Lit Web Components. Features ribbon and badge support, optimized for S3-hosted transparent background images.

Features

  • Tiny bundle (~6KB gzipped including Lit runtime)
  • Zero framework dependencies Works with React, Vue, Angular, Svelte, and vanilla JS
  • Tree-shakeable ESM exports
  • Performance-first Native lazy loading, async decoding, CSS shimmer
  • Fully customizable Variants, ribbons, badges, borders, backgrounds
  • TypeScript Full type definitions included
  • Tailwind compatible Uses Light DOM for seamless Tailwind integration
  • shadcn compatible Install via CLI

Why Lit?

This component uses Lit web components as its core, providing:

  • ✅ Works natively in all frameworks (React, Vue, Angular, Svelte)
  • ✅ No peer dependencies (unlike React-based components)
  • ✅ Smaller bundle size for non-React users
  • ✅ No wrapper code complexity
  • ✅ Full Tailwind CSS support via Light DOM

Installation

npm

npm install @grasco/profile-picture
# or
bun add @grasco/profile-picture

shadcn CLI

bunx shadcn add @grasco/profile-picture-03

Quick Start (Plug-and-Play)

Simply import the component and its styles - no Tailwind configuration required:

// Import the CSS (required once, at app entry point)
import '@grasco/profile-picture/styles.css';

// Import the component
import { ProfilePicture } from '@grasco/profile-picture';

The CSS file includes all Tailwind utility classes used by the component. No additional setup needed.

Usage

React

import '@grasco/profile-picture/styles.css';
import { ProfilePicture } from '@grasco/profile-picture';

function App() {
  return (
    <ProfilePicture
      src="https://your-bucket.s3.amazonaws.com/avatar.png"
      alt="John Doe"
      size="lg"
      variant="circle"
      border
      borderColor="white"
      bgColor="bg-gradient-to-br from-purple-500 to-pink-500"
      ribbon={{ text: 'PRO', position: 'top-right', bgColor: 'bg-amber-500' }}
      badge={{ content: '3', position: 'bottom-right', pulse: true }}
      onLoad={() => console.log('loaded')}
      onError={() => console.error('failed')}
    />
  );
}

Vue

<script setup lang="ts">
import '@grasco/profile-picture/styles.css';
import '@grasco/profile-picture/vue';

const avatarUrl = 'https://example.com/avatar.png';
const ribbon = { text: 'PRO', position: 'top-right', bgColor: 'bg-amber-500' };
const badge = { content: '3', position: 'bottom-right', pulse: true };
</script>

<template>
  <profile-picture
    :src="avatarUrl"
    alt="John Doe"
    size="lg"
    variant="circle"
    :border="true"
    border-color="white"
    bg-color="bg-gradient-to-br from-purple-500 to-pink-500"
    :ribbon="ribbon"
    :badge="badge"
    @load="handleLoad"
    @error="handleError"
  />
</template>

Note: Add to vite.config.ts:

export default {
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) => tag === 'profile-picture'
        }
      }
    })
  ]
}

Angular

import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import '@grasco/profile-picture/styles.css';
import '@grasco/profile-picture/angular';

@Component({
  selector: 'app-example',
  standalone: true,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <profile-picture
      [src]="avatarUrl"
      alt="John Doe"
      size="lg"
      variant="circle"
      [border]="true"
      border-color="white"
      bg-color="bg-gradient-to-br from-purple-500 to-pink-500"
      [ribbon]="ribbon"
      [badge]="badge"
      (load)="onLoad()"
      (error)="onError()">
    </profile-picture>
  `
})
export class ExampleComponent {
  avatarUrl = 'https://example.com/avatar.png';
  ribbon = { text: 'PRO', position: 'top-right', bgColor: 'bg-amber-500' };
  badge = { content: '3', position: 'bottom-right', pulse: true };

  onLoad() {
    console.log('Image loaded');
  }

  onError() {
    console.error('Image failed');
  }
}

Svelte

<script lang="ts">
  import '@grasco/profile-picture/styles.css';
  import '@grasco/profile-picture/svelte';

  let avatarUrl = 'https://example.com/avatar.png';
  const ribbon = { text: 'PRO', position: 'top-right', bgColor: 'bg-amber-500' };
  const badge = { content: '3', position: 'bottom-right', pulse: true };
</script>

<profile-picture
  src={avatarUrl}
  alt="John Doe"
  size="lg"
  variant="circle"
  border={true}
  border-color="white"
  bg-color="bg-gradient-to-br from-purple-500 to-pink-500"
  ribbon={ribbon}
  badge={badge}
  on:load={handleLoad}
  on:error={handleError}
/>

Vanilla JavaScript

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="node_modules/@grasco/profile-picture/dist/styles.css">
  <script type="module">
    import '@grasco/profile-picture';
  </script>
</head>
<body>
  <profile-picture
    src="https://example.com/avatar.png"
    alt="John Doe"
    size="lg"
    variant="circle"
    border
    border-color="white"
    bg-color="bg-gradient-to-br from-purple-500 to-pink-500">
  </profile-picture>

  <script>
    const pic = document.querySelector('profile-picture');
    pic.ribbon = { text: 'PRO', position: 'top-right', bgColor: 'bg-amber-500' };
    pic.badge = { content: '3', position: 'bottom-right', pulse: true };
    pic.addEventListener('load', () => console.log('loaded'));
  </script>
</body>
</html>

Props / Attributes

| Prop | Type | Default | Description | |------|------|---------|-------------| | src | string | required | Image URL (S3 or any public URL) | | alt | string | '' | Alt text for accessibility | | size | 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| number | 'md' | Size preset or custom pixels | | variant | 'circle' \| 'rounded' \| 'square' | 'circle' | Shape variant | | border | boolean | false | Enable border | | border-width | 1 \| 2 \| 3 \| 4 | 2 | Border width in pixels | | border-color | string | 'white' | Tailwind class or hex color | | bg-color | string | 'bg-gray-100' | Background color for transparent images | | bg-gradient | string | - | Tailwind gradient class | | ribbon | RibbonConfig | - | Ribbon configuration (object) | | badge | BadgeConfig | - | Badge configuration (object) | | loading | 'lazy' \| 'eager' | 'lazy' | Image loading strategy | | placeholder | 'shimmer' \| 'blur' \| 'none' | 'shimmer' | Placeholder type | | placeholder-color | string | '#e5e7eb' | Placeholder background color | | fallback | string | - | Custom fallback text |

RibbonConfig

interface RibbonConfig {
  text: string;
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
  color?: string;     // Text color (Tailwind or hex)
  bgColor?: string;   // Background color (Tailwind or hex)
}

BadgeConfig

interface BadgeConfig {
  content?: string | number;  // Text, number, or empty for dot
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
  color?: string;       // Text color (Tailwind or hex)
  bgColor?: string;     // Background color (Tailwind or hex)
  borderRadius?: string; // Border radius (CSS value, e.g., "8px", "50%", "9999px")
  pulse?: boolean;      // Enable pulse animation
  glow?: boolean;       // Enable glow effect
  max?: number;         // Max value to display (shows 99+ if exceeded)
  icon?: string;        // Icon to display before content
}

Events

| Event | Description | |-------|-------------| | load | Fired when image loads successfully | | error | Fired when image fails to load |

Size Presets

| Size | Pixels | |------|--------| | xs | 24px | | sm | 32px | | md | 40px | | lg | 56px | | xl | 80px |

Tailwind Setup (Optional)

Note: If you imported @grasco/profile-picture/styles.css, you already have all the styles needed. The setup below is only for projects that want to use their own Tailwind configuration.

This component uses Light DOM (no Shadow DOM) so Tailwind classes work seamlessly.

Tailwind v4 (Recommended)

// tailwind.config.js
import { safelist } from '@grasco/profile-picture/tailwind.safelist';

export default {
  content: [
    './src/**/*.{html,js,ts,jsx,tsx,vue,svelte}',
    './node_modules/@grasco/profile-picture/dist/**/*.{js,ts}'
  ],
  safelist, // Optional: only if you pass incomplete class names
}

Tailwind v3

// tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{html,js,ts,jsx,tsx,vue,svelte}',
    './node_modules/@grasco/profile-picture/**/*.{js,ts}'
  ],
}

Tree Shaking Best Practices

For optimal tree shaking, always use complete Tailwind class names:

// ✅ GOOD - Classes detected by Tailwind scanner
<ProfilePicture
  bgColor="bg-blue-500"
  borderColor="border-white"
  bgGradient="bg-gradient-to-br from-purple-500 to-pink-500"
/>

// ⚠️ WORKS but requires safelist configuration
<ProfilePicture
  bgColor="blue-500"      // Missing "bg-" prefix
  borderColor="white"     // Missing "border-" prefix
/>

// ✅ BEST - Uses inline styles, no Tailwind needed
<ProfilePicture
  bgColor="#3b82f6"
  borderColor="#ffffff"
/>

Recommendation: Use hex colors for dynamic values and complete class names for static values.

Performance Tips

  1. Use lazy loading (default) for below-the-fold images
  2. Set explicit sizes to prevent layout shift
  3. Leverage S3 caching with proper cache headers
  4. Use placeholder="none" if you have instant images
  5. Preload critical images with <link rel="preload">

Bundle Size

| Package | Size (gzipped) | |---------|----------------| | Lit runtime | ~6KB | | Component code | <1KB | | Total | ~6KB |

Compare to React-based alternatives:

  • React + ReactDOM: ~40KB
  • This component: 6KB (85% smaller)

Browser Support

  • Chrome/Edge 67+
  • Firefox 63+
  • Safari 13.1+
  • All modern browsers with Web Components support

For older browsers, include the webcomponents polyfill.

License

MIT

Links