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
98 changes: 98 additions & 0 deletions modal-dialog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# ModalDialog
A modal dialog component that displays as a centered overlay with a semi-transparent backdrop.

## 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 for targeting the modal container |
| Is Open | Boolean | false | Controls whether the modal is currently open or closed |
| Max Width | Variant | medium | Maximum width of the modal dialog (small, medium, large) |
| Close On Backdrop Click | Boolean | true | Whether clicking the backdrop closes the modal |
| Trigger Button Text | Text | Open Modal | Text displayed on the button that opens the modal |
| Modal Title | TextNode | Modal Title | Main heading displayed at the top of the modal |
| Body Content | RichText | This is the modal body content... | Rich text content displayed in the modal body |
| Content Slot | Slot | — | Optional slot for custom component content instead of rich text |
| Show Close Button | Visibility | — | Show or hide the close button in the top-right corner |
| Close Button Label | Text | Close modal | Accessible label for the close button |
| Animation Duration | Number | 300 | Duration of open/close animation in milliseconds |
| Enable Escape Key | Boolean | true | Whether pressing Escape key closes the modal |
| Show Trigger Button | Visibility | — | Show or hide the trigger 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 |
|---------------|------------------|----------|
| --background-primary | Modal container background color | #ffffff |
| --background-secondary | Trigger button hover state background | #f5f5f5 |
| --text-primary | Modal title and body text color | #1a1a1a |
| --text-secondary | Close button icon color | #737373 |
| --border-color | Modal container and header border color | #e5e5e5 |
| --accent-color | Trigger button background and link color | #1a1a1a |
| --accent-text-color | Trigger button text color | #ffffff |
| --border-radius | Modal and button corner rounding | 8px |

### Inherited Properties

The component inherits these CSS properties from its parent element:
- `font-family` — Typography style
- `color` — Text color
- `line-height` — Text spacing

## Extending in Code

### Custom Modal Width Variants

Modify the max-width values for different size variants by targeting the component's CSS custom property:

```css
/* Small modal */
.wf-modaldialog[data-max-width="small"] {
--wf-modaldialog-max-width: 400px;
}

/* Large modal */
.wf-modaldialog[data-max-width="large"] {
--wf-modaldialog-max-width: 900px;
}
```

### Programmatic Modal Control

Control the modal state through custom interactions by targeting the component's ID:

```javascript
// Open modal
const modal = document.querySelector('#my-modal');
modal.setAttribute('data-is-open', 'true');

// Close modal
modal.setAttribute('data-is-open', 'false');
```

## Dependencies

No external dependencies.
17 changes: 17 additions & 0 deletions modal-dialog/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>ModalDialog</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 modal-dialog/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Modal Dialog",
"description": "Accessible overlay dialog with backdrop and focus trapping",
"category": "Overlay"
}
25 changes: 25 additions & 0 deletions modal-dialog/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "modal-dialog",
"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"
}
}
1 change: 1 addition & 0 deletions modal-dialog/screenshot-brand.b64

Large diffs are not rendered by default.

Binary file added modal-dialog/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.
1 change: 1 addition & 0 deletions modal-dialog/screenshot-dark.b64

Large diffs are not rendered by default.

Binary file added modal-dialog/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.
1 change: 1 addition & 0 deletions modal-dialog/screenshot-light.b64

Large diffs are not rendered by default.

Binary file added modal-dialog/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.
225 changes: 225 additions & 0 deletions modal-dialog/src/components/ModalDialog/ModalDialog.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
/*
* Webflow Site Variables Used:
* - --background-primary: Modal container background
* - --background-secondary: Trigger button hover state
* - --text-primary: Modal title and body text
* - --text-secondary: Close button icon color
* - --border-color: Modal container border
* - --accent-color: Trigger button background
* - --accent-text-color: Trigger button text
* - --border-radius: Modal and button rounding
*/

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

/* Root element - inherit Webflow typography + default padding */
.wf-modaldialog {
font-family: inherit;
color: inherit;
line-height: inherit;
padding: 24px;
--wf-modaldialog-max-width: 600px;
--wf-modaldialog-animation-duration: 300ms;
}

/* Trigger button */
.wf-modaldialog-trigger {
background: var(--accent-color, #1a1a1a);
color: var(--accent-text-color, #ffffff);
border: none;
padding: 12px 24px;
border-radius: var(--border-radius, 8px);
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s, transform 0.1s;
}

.wf-modaldialog-trigger:hover {
background: var(--text-primary, #1a1a1a);
transform: translateY(-1px);
}

.wf-modaldialog-trigger:focus-visible {
outline: 2px solid var(--accent-color, #1a1a1a);
outline-offset: 2px;
}

.wf-modaldialog-trigger:active {
transform: translateY(0);
}

.wf-modaldialog-trigger:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}

/* Overlay backdrop */
.wf-modaldialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
padding: 16px;
animation: wf-modaldialog-fade-in var(--wf-modaldialog-animation-duration) ease-out;
}

@keyframes wf-modaldialog-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

/* Modal container */
.wf-modaldialog-container {
background: var(--background-primary, #ffffff);
border: 1px solid var(--border-color, #e5e5e5);
border-radius: var(--border-radius, 8px);
max-width: var(--wf-modaldialog-max-width);
width: 100%;
max-height: calc(100vh - 32px);
overflow: auto;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
animation: wf-modaldialog-scale-in var(--wf-modaldialog-animation-duration) ease-out;
}

@keyframes wf-modaldialog-scale-in {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}

/* Modal header */
.wf-modaldialog-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
padding: 24px 24px 16px 24px;
border-bottom: 1px solid var(--border-color, #e5e5e5);
}

/* Modal title */
.wf-modaldialog-title {
margin: 0;
font-size: 24px;
font-weight: 600;
color: var(--text-primary, #1a1a1a);
line-height: 1.3;
flex: 1;
}

/* Close button */
.wf-modaldialog-close {
background: transparent;
border: none;
padding: 4px;
cursor: pointer;
border-radius: var(--border-radius, 8px);
color: var(--text-secondary, #737373);
transition: background-color 0.2s, color 0.2s;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}

.wf-modaldialog-close:hover {
background: var(--background-secondary, #f5f5f5);
color: var(--text-primary, #1a1a1a);
}

.wf-modaldialog-close:focus-visible {
outline: 2px solid var(--accent-color, #1a1a1a);
outline-offset: 2px;
}

.wf-modaldialog-close:active {
transform: scale(0.95);
}

.wf-modaldialog-close:disabled {
opacity: 0.5;
cursor: not-allowed;
}

/* Close button icon */
.wf-modaldialog-close-icon {
width: 24px;
height: 24px;
display: block;
}

/* Modal body */
.wf-modaldialog-body {
padding: 24px;
color: var(--text-primary, #1a1a1a);
}

/* Body content (rich text) */
.wf-modaldialog-content {
line-height: 1.6;
}

.wf-modaldialog-content p {
margin: 0 0 16px 0;
}

.wf-modaldialog-content p:last-child {
margin-bottom: 0;
}

.wf-modaldialog-content h1,
.wf-modaldialog-content h2,
.wf-modaldialog-content h3,
.wf-modaldialog-content h4,
.wf-modaldialog-content h5,
.wf-modaldialog-content h6 {
margin: 0 0 12px 0;
font-weight: 600;
color: var(--text-primary, #1a1a1a);
}

.wf-modaldialog-content ul,
.wf-modaldialog-content ol {
margin: 0 0 16px 0;
padding-left: 24px;
}

.wf-modaldialog-content li {
margin-bottom: 8px;
}

.wf-modaldialog-content a {
color: var(--accent-color, #1a1a1a);
text-decoration: underline;
}

.wf-modaldialog-content a:hover {
text-decoration: none;
}

/* Content slot */
.wf-modaldialog-slot {
width: 100%;
}
Loading