@grasco/profile-picture
v0.2.0
Published
Lightweight, tree-shakeable profile picture component with ribbon and badge support
Maintainers
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-pictureshadcn CLI
bunx shadcn add @grasco/profile-picture-03Quick 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
- Use lazy loading (default) for below-the-fold images
- Set explicit sizes to prevent layout shift
- Leverage S3 caching with proper cache headers
- Use
placeholder="none"if you have instant images - 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
