Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions badge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Badge
A compact badge component that displays a label with visual styling variants for different semantic meanings.

## Getting Started

Install dependencies:
```bash
npm install
```

Share the component to your Webflow workspace:
```bash
npx webflow library share
```

For local development:
```bash
npm run dev
```

## Designer Properties

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| ID | Id | — | HTML ID attribute for targeting and accessibility |
| Label | TextNode | Badge | The text content displayed in the badge |
| Variant | Variant | info | Visual style variant for semantic meaning (success, warning, error, info) |
| Size | Variant | md | Size of the badge (sm, md, lg) |
| Show Close Button | Boolean | false | Whether to display the close button on the right side |
| Close Button Aria Label | Text | Remove badge | Accessible label for the close button |

## Styling

This component automatically adapts to your Webflow site's design system through site variables and inherited properties.

### Site Variables

To match your site's design system, define these CSS variables in your Webflow project settings. The component will use the fallback values shown below until you configure them.

| Site Variable | What It Controls | Fallback |
|---------------|------------------|----------|
| --border-radius | Badge corner rounding | 8px |

### Inherited Properties

The component inherits these CSS properties from its parent element:
- `font-family` — Typography style
- `color` — Text color (overridden by variant colors)
- `line-height` — Text spacing

## Extending in Code

### Custom Close Button Handler

Add custom behavior when the close button is clicked:

```javascript
document.addEventListener('DOMContentLoaded', () => {
const badges = document.querySelectorAll('.wf-badge');

badges.forEach(badge => {
const closeButton = badge.querySelector('.wf-badge-close');
if (closeButton) {
closeButton.addEventListener('click', () => {
// Fade out animation
badge.style.transition = 'opacity 0.2s';
badge.style.opacity = '0';

// Remove after animation
setTimeout(() => {
badge.remove();
}, 200);
});
}
});
});
```

### Dynamic Badge Creation

Programmatically create badges with different variants:

```javascript
function createBadge(label, variant = 'info', showClose = false) {
const badge = document.createElement('div');
badge.className = `wf-badge wf-badge-${variant} wf-badge-md`;

const labelSpan = document.createElement('span');
labelSpan.className = 'wf-badge-label';
labelSpan.textContent = label;
badge.appendChild(labelSpan);

if (showClose) {
const closeBtn = document.createElement('button');
closeBtn.className = 'wf-badge-close';
closeBtn.setAttribute('aria-label', 'Remove badge');
closeBtn.innerHTML = '<svg class="wf-badge-close-icon" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 2l8 8M10 2l-8 8"/></svg>';
badge.appendChild(closeBtn);
}

return badge;
}

// Usage
const container = document.getElementById('badge-container');
container.appendChild(createBadge('New', 'success', true));
container.appendChild(createBadge('Pending', 'warning'));
```

## Dependencies

No external dependencies.
17 changes: 17 additions & 0 deletions badge/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Badge</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; }
body { color: inherit; }
h1, h2, h3, h4, h5, h6 { color: inherit; }
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
5 changes: 5 additions & 0 deletions badge/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Badge",
"description": "Small status labels with color variants for tags and counts",
"category": "Data Display"
}
25 changes: 25 additions & 0 deletions badge/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "badge",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.1.1",
"react-dom": "^19.1.1"
},
"devDependencies": {
"@types/react": "^19.1.13",
"@types/react-dom": "^19.1.9",
"@vitejs/plugin-react": "^5.0.3",
"@webflow/data-types": "^1.0.1",
"@webflow/react": "^1.0.1",
"@webflow/webflow-cli": "^1.8.44",
"typescript": "~5.8.3",
"vite": "^7.1.7"
}
}
Binary file added badge/screenshot-brand.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added badge/screenshot-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added badge/screenshot-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
128 changes: 128 additions & 0 deletions badge/src/components/Badge/Badge.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Webflow Site Variables Used:
* - --text-primary: Badge text color
* - --border-radius: Badge corner rounding
* - --background-primary: Base background for badges
* - --background-secondary: Hover states
* - --border-color: Badge borders
*/

/* Box sizing reset */
.wf-badge *,
.wf-badge *::before,
.wf-badge *::after {
box-sizing: border-box;
}

/* Root element - inherit Webflow typography */
.wf-badge {
font-family: inherit;
color: inherit;
line-height: inherit;
display: inline-flex;
align-items: center;
gap: 6px;
border-radius: var(--border-radius, 8px);
font-weight: 500;
white-space: nowrap;
border: 1px solid transparent;
}

/* Size variants */
.wf-badge-sm {
padding: 2px 8px;
font-size: 12px;
gap: 4px;
}

.wf-badge-md {
padding: 4px 12px;
font-size: 14px;
gap: 6px;
}

.wf-badge-lg {
padding: 6px 16px;
font-size: 16px;
gap: 8px;
}

/* Color variants - Success */
.wf-badge-success {
background: #dcfce7;
color: #166534;
border-color: #bbf7d0;
}

/* Color variants - Warning */
.wf-badge-warning {
background: #fef3c7;
color: #92400e;
border-color: #fde68a;
}

/* Color variants - Error */
.wf-badge-error {
background: #fee2e2;
color: #991b1b;
border-color: #fecaca;
}

/* Color variants - Info */
.wf-badge-info {
background: #dbeafe;
color: #1e40af;
border-color: #bfdbfe;
}

/* Label */
.wf-badge-label {
display: inline-block;
}

/* Close button */
.wf-badge-close {
display: inline-flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
padding: 0;
margin: 0;
cursor: pointer;
color: inherit;
opacity: 0.7;
transition: opacity 0.2s;
border-radius: 2px;
}

.wf-badge-close:hover {
opacity: 1;
}

.wf-badge-close:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
opacity: 1;
}

.wf-badge-close:active {
opacity: 0.8;
}

/* Close icon */
.wf-badge-close-icon {
display: block;
width: 12px;
height: 12px;
}

.wf-badge-sm .wf-badge-close-icon {
width: 10px;
height: 10px;
}

.wf-badge-lg .wf-badge-close-icon {
width: 14px;
height: 14px;
}
64 changes: 64 additions & 0 deletions badge/src/components/Badge/Badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useState } from "react";

export interface BadgeProps {
id?: string;
label?: string;
variant?: "success" | "warning" | "error" | "info";
size?: "sm" | "md" | "lg";
showCloseButton?: boolean;
closeButtonAriaLabel?: string;
}

export default function Badge({
id,
label = "Badge",
variant = "info",
size = "md",
showCloseButton = false,
closeButtonAriaLabel = "Remove badge",
}: BadgeProps) {
const [isVisible, setIsVisible] = useState(true);

const handleClose = () => {
setIsVisible(false);
};

if (!isVisible) {
return null;
}

return (
<span
id={id}
className={`wf-badge wf-badge-${variant} wf-badge-${size}`}
>
<span className="wf-badge-label">{label}</span>
{showCloseButton && (
<button
type="button"
className="wf-badge-close"
onClick={handleClose}
aria-label={closeButtonAriaLabel}
>
<svg
className="wf-badge-close-icon"
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M9 3L3 9M3 3L9 9"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
)}
</span>
);
}
Loading