UI Components Easy
Message Block
Colored message blocks with optional header, body, and dismiss button in info, success, warning, and danger variants with fade-out animation.
Open in Lab
MCP
css javascript vue svelte
Targets: TS JS HTML React Vue Svelte
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Inter, system-ui, sans-serif;
background: #0a0a0a;
color: #f2f6ff;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.demo {
width: 100%;
max-width: 600px;
}
.demo-title {
font-size: 1.5rem;
font-weight: 800;
margin-bottom: 0.375rem;
}
.demo-sub {
color: #475569;
font-size: 0.875rem;
margin-bottom: 2rem;
}
.msg-stack {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
/* ── Message Block ── */
.msg {
background: var(--msg-bg);
border: 1px solid var(--msg-border);
border-left: 4px solid var(--msg-color);
border-radius: 0.75rem;
overflow: hidden;
transition: opacity 0.3s ease, transform 0.3s ease, max-height 0.3s ease, margin 0.3s ease,
padding 0.3s ease;
}
.msg--dismissing {
opacity: 0;
transform: translateX(12px) scale(0.98);
max-height: 0 !important;
margin-top: 0;
margin-bottom: -0.75rem;
padding-top: 0;
padding-bottom: 0;
border-width: 0;
}
/* ── Variants ── */
.msg--info {
--msg-color: #38bdf8;
--msg-bg: rgba(56, 189, 248, 0.06);
--msg-border: rgba(56, 189, 248, 0.12);
}
.msg--success {
--msg-color: #22c55e;
--msg-bg: rgba(34, 197, 94, 0.06);
--msg-border: rgba(34, 197, 94, 0.12);
}
.msg--warning {
--msg-color: #f59e0b;
--msg-bg: rgba(245, 158, 11, 0.06);
--msg-border: rgba(245, 158, 11, 0.12);
}
.msg--danger {
--msg-color: #ef4444;
--msg-bg: rgba(239, 68, 68, 0.06);
--msg-border: rgba(239, 68, 68, 0.12);
}
/* ── Header ── */
.msg-header {
display: flex;
align-items: flex-start;
gap: 0.625rem;
padding: 0.875rem 1rem;
}
.msg-icon {
font-size: 1rem;
color: var(--msg-color);
flex-shrink: 0;
line-height: 1.5;
}
.msg-title {
flex: 1;
font-size: 0.875rem;
font-weight: 700;
color: #f2f6ff;
line-height: 1.5;
}
/* ── Body ── */
.msg-body {
padding: 0 1rem 0.875rem 2.625rem;
font-size: 0.8125rem;
line-height: 1.6;
color: #94a3b8;
}
.msg-body--inline {
padding: 0;
flex: 1;
line-height: 1.5;
}
.msg-body code {
font-family: "JetBrains Mono", "Fira Code", monospace;
font-size: 0.75rem;
background: rgba(255, 255, 255, 0.06);
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
color: #f2f6ff;
}
.msg-body kbd {
font-family: "JetBrains Mono", "Fira Code", monospace;
font-size: 0.6875rem;
background: rgba(255, 255, 255, 0.08);
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
border: 1px solid rgba(255, 255, 255, 0.1);
color: #f2f6ff;
}
/* ── Dismiss ── */
.msg-dismiss {
flex-shrink: 0;
background: none;
border: none;
color: #4a4a4a;
cursor: pointer;
font-size: 1.25rem;
line-height: 1;
padding: 0.125rem 0.25rem;
border-radius: 0.25rem;
transition: color 0.15s, background 0.15s;
margin-left: auto;
}
.msg-dismiss:hover {
color: #f2f6ff;
background: rgba(255, 255, 255, 0.06);
}(function () {
document.querySelectorAll(".msg-dismiss").forEach(function (btn) {
btn.addEventListener("click", function () {
var msg = btn.closest(".msg");
if (!msg) return;
/* Lock current height so CSS can transition it */
msg.style.maxHeight = msg.offsetHeight + "px";
requestAnimationFrame(function () {
msg.classList.add("msg--dismissing");
});
msg.addEventListener("transitionend", function handler(e) {
if (e.propertyName === "opacity") {
msg.remove();
}
});
});
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Message Block</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="demo">
<h1 class="demo-title">Message Block</h1>
<p class="demo-sub">Colored message blocks with dismiss animation.</p>
<div class="msg-stack">
<!-- Info with title -->
<div class="msg msg--info" role="alert">
<div class="msg-header">
<span class="msg-icon">ⓘ</span>
<span class="msg-title">New Feature Available</span>
<button class="msg-dismiss" aria-label="Dismiss">×</button>
</div>
<div class="msg-body">
We just launched dark mode support across all components. Toggle it from the settings panel or use the keyboard shortcut <kbd>Ctrl+D</kbd>.
</div>
</div>
<!-- Success with title -->
<div class="msg msg--success" role="alert">
<div class="msg-header">
<span class="msg-icon">✓</span>
<span class="msg-title">Deployment Complete</span>
<button class="msg-dismiss" aria-label="Dismiss">×</button>
</div>
<div class="msg-body">
Your application has been successfully deployed to production. All health checks passed and the CDN cache has been purged.
</div>
</div>
<!-- Warning with title -->
<div class="msg msg--warning" role="alert">
<div class="msg-header">
<span class="msg-icon">⚠</span>
<span class="msg-title">API Rate Limit</span>
<button class="msg-dismiss" aria-label="Dismiss">×</button>
</div>
<div class="msg-body">
You have used 85% of your API quota for this billing period. Consider upgrading your plan to avoid service interruptions.
</div>
</div>
<!-- Danger with title -->
<div class="msg msg--danger" role="alert">
<div class="msg-header">
<span class="msg-icon">✗</span>
<span class="msg-title">Build Failed</span>
<button class="msg-dismiss" aria-label="Dismiss">×</button>
</div>
<div class="msg-body">
The production build failed due to a type error in <code>src/components/Dashboard.tsx</code>. Check the build logs for details.
</div>
</div>
<!-- Info without title -->
<div class="msg msg--info" role="alert">
<div class="msg-header">
<span class="msg-icon">ⓘ</span>
<div class="msg-body msg-body--inline">
This is a compact info message without a separate title. Dismiss it with the button on the right.
</div>
<button class="msg-dismiss" aria-label="Dismiss">×</button>
</div>
</div>
<!-- Success without dismiss -->
<div class="msg msg--success" role="alert">
<div class="msg-header">
<span class="msg-icon">✓</span>
<div class="msg-body msg-body--inline">
This success message cannot be dismissed. It stays visible permanently.
</div>
</div>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>import { useState, useRef, ReactNode } from "react";
type MessageVariant = "info" | "success" | "warning" | "danger";
interface MessageBlockProps {
variant?: MessageVariant;
title?: string;
children: ReactNode;
onDismiss?: () => void;
dismissible?: boolean;
}
const variantStyles: Record<
MessageVariant,
{ color: string; bg: string; border: string; icon: string }
> = {
info: {
color: "#38bdf8",
bg: "rgba(56,189,248,0.06)",
border: "rgba(56,189,248,0.12)",
icon: "\u2139",
},
success: {
color: "#22c55e",
bg: "rgba(34,197,94,0.06)",
border: "rgba(34,197,94,0.12)",
icon: "\u2713",
},
warning: {
color: "#f59e0b",
bg: "rgba(245,158,11,0.06)",
border: "rgba(245,158,11,0.12)",
icon: "\u26A0",
},
danger: {
color: "#ef4444",
bg: "rgba(239,68,68,0.06)",
border: "rgba(239,68,68,0.12)",
icon: "\u2717",
},
};
export function MessageBlock({
variant = "info",
title,
children,
onDismiss,
dismissible = true,
}: MessageBlockProps) {
const [dismissed, setDismissed] = useState(false);
const [dismissing, setDismissing] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const v = variantStyles[variant];
const handleDismiss = () => {
if (ref.current) {
ref.current.style.maxHeight = ref.current.offsetHeight + "px";
}
setDismissing(true);
setTimeout(() => {
setDismissed(true);
onDismiss?.();
}, 300);
};
if (dismissed) return null;
return (
<div
ref={ref}
role="alert"
style={{
background: v.bg,
border: `1px solid ${v.border}`,
borderLeft: `4px solid ${v.color}`,
borderRadius: "0.75rem",
overflow: "hidden",
transition: "opacity 0.3s ease, transform 0.3s ease, max-height 0.3s ease",
opacity: dismissing ? 0 : 1,
transform: dismissing ? "translateX(12px) scale(0.98)" : "none",
maxHeight: dismissing ? 0 : undefined,
}}
>
<div
style={{
display: "flex",
alignItems: "flex-start",
gap: "0.625rem",
padding: "0.875rem 1rem",
}}
>
<span style={{ fontSize: "1rem", color: v.color, flexShrink: 0, lineHeight: 1.5 }}>
{v.icon}
</span>
{title ? (
<span
style={{
flex: 1,
fontSize: "0.875rem",
fontWeight: 700,
color: "#f2f6ff",
lineHeight: 1.5,
}}
>
{title}
</span>
) : (
<div style={{ flex: 1, fontSize: "0.8125rem", lineHeight: 1.6, color: "#94a3b8" }}>
{children}
</div>
)}
{dismissible && (
<button
type="button"
onClick={handleDismiss}
aria-label="Dismiss"
style={{
flexShrink: 0,
background: "none",
border: "none",
color: "#4a4a4a",
cursor: "pointer",
fontSize: "1.25rem",
lineHeight: 1,
padding: "0.125rem 0.25rem",
borderRadius: "0.25rem",
marginLeft: "auto",
}}
>
×
</button>
)}
</div>
{title && (
<div
style={{
padding: "0 1rem 0.875rem 2.625rem",
fontSize: "0.8125rem",
lineHeight: 1.6,
color: "#94a3b8",
}}
>
{children}
</div>
)}
</div>
);
}
/* Demo */
export default function MessageBlockDemo() {
return (
<div
style={{
minHeight: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "#0a0a0a",
fontFamily: "Inter, system-ui, sans-serif",
color: "#f2f6ff",
padding: "2rem",
}}
>
<div
style={{
width: "100%",
maxWidth: 600,
display: "flex",
flexDirection: "column",
gap: "0.75rem",
}}
>
<h1 style={{ fontSize: "1.5rem", fontWeight: 800, marginBottom: "0.375rem" }}>
Message Block
</h1>
<p style={{ color: "#475569", fontSize: "0.875rem", marginBottom: "1rem" }}>
Colored message blocks with dismiss animation.
</p>
<MessageBlock variant="info" title="New Feature Available">
We just launched dark mode support across all components. Toggle it from the settings
panel or use the keyboard shortcut{" "}
<code
style={{
fontFamily: '"JetBrains Mono", "Fira Code", monospace',
fontSize: "0.75rem",
background: "rgba(255,255,255,0.06)",
padding: "0.125rem 0.375rem",
borderRadius: "0.25rem",
color: "#f2f6ff",
}}
>
Ctrl+D
</code>
.
</MessageBlock>
<MessageBlock variant="success" title="Deployment Complete">
Your application has been successfully deployed to production. All health checks passed
and the CDN cache has been purged.
</MessageBlock>
<MessageBlock variant="warning" title="API Rate Limit">
You have used 85% of your API quota for this billing period. Consider upgrading your plan
to avoid service interruptions.
</MessageBlock>
<MessageBlock variant="danger" title="Build Failed">
The production build failed due to a type error in{" "}
<code
style={{
fontFamily: '"JetBrains Mono", "Fira Code", monospace',
fontSize: "0.75rem",
background: "rgba(255,255,255,0.06)",
padding: "0.125rem 0.375rem",
borderRadius: "0.25rem",
color: "#f2f6ff",
}}
>
src/components/Dashboard.tsx
</code>
. Check the build logs for details.
</MessageBlock>
<MessageBlock variant="info">
This is a compact info message without a separate title. Dismiss it with the button on the
right.
</MessageBlock>
<MessageBlock variant="success" dismissible={false}>
This success message cannot be dismissed. It stays visible permanently.
</MessageBlock>
</div>
</div>
);
}<script setup>
import { ref } from "vue";
const variantStyles = {
info: {
color: "#38bdf8",
bg: "rgba(56,189,248,0.06)",
border: "rgba(56,189,248,0.12)",
icon: "\u2139",
},
success: {
color: "#22c55e",
bg: "rgba(34,197,94,0.06)",
border: "rgba(34,197,94,0.12)",
icon: "\u2713",
},
warning: {
color: "#f59e0b",
bg: "rgba(245,158,11,0.06)",
border: "rgba(245,158,11,0.12)",
icon: "\u26A0",
},
danger: {
color: "#ef4444",
bg: "rgba(239,68,68,0.06)",
border: "rgba(239,68,68,0.12)",
icon: "\u2717",
},
};
const messages = ref([
{
id: 1,
variant: "info",
title: "New Feature Available",
body: "We just launched dark mode support across all components. Toggle it from the settings panel or use the keyboard shortcut Ctrl+D.",
dismissible: true,
},
{
id: 2,
variant: "success",
title: "Deployment Complete",
body: "Your application has been successfully deployed to production. All health checks passed and the CDN cache has been purged.",
dismissible: true,
},
{
id: 3,
variant: "warning",
title: "API Rate Limit",
body: "You have used 85% of your API quota for this billing period. Consider upgrading your plan to avoid service interruptions.",
dismissible: true,
},
{
id: 4,
variant: "danger",
title: "Build Failed",
body: "The production build failed due to a type error in src/components/Dashboard.tsx. Check the build logs for details.",
dismissible: true,
},
{
id: 5,
variant: "info",
title: "",
body: "This is a compact info message without a separate title. Dismiss it with the button on the right.",
dismissible: true,
},
{
id: 6,
variant: "success",
title: "",
body: "This success message cannot be dismissed. It stays visible permanently.",
dismissible: false,
},
]);
const dismissing = ref({});
const dismissed = ref({});
function handleDismiss(id) {
dismissing.value[id] = true;
setTimeout(() => {
dismissed.value[id] = true;
}, 300);
}
function v(msg) {
return variantStyles[msg.variant];
}
</script>
<template>
<div style="min-height:100vh;display:flex;align-items:center;justify-content:center;background:#0a0a0a;font-family:Inter,system-ui,sans-serif;color:#f2f6ff;padding:2rem">
<div style="width:100%;max-width:600px;display:flex;flex-direction:column;gap:0.75rem">
<h1 style="font-size:1.5rem;font-weight:800;margin-bottom:0.375rem">Message Block</h1>
<p style="color:#475569;font-size:0.875rem;margin-bottom:1rem">Colored message blocks with dismiss animation.</p>
<template v-for="msg in messages" :key="msg.id">
<div
v-if="!dismissed[msg.id]"
role="alert"
:style="{
background: v(msg).bg,
border: `1px solid ${v(msg).border}`,
borderLeft: `4px solid ${v(msg).color}`,
borderRadius: '0.75rem',
overflow: 'hidden',
transition: 'opacity 0.3s ease, transform 0.3s ease, max-height 0.3s ease',
opacity: dismissing[msg.id] ? 0 : 1,
transform: dismissing[msg.id] ? 'translateX(12px) scale(0.98)' : 'none',
maxHeight: dismissing[msg.id] ? '0px' : '500px',
}"
>
<div style="display:flex;align-items:flex-start;gap:0.625rem;padding:0.875rem 1rem">
<span :style="{ fontSize: '1rem', color: v(msg).color, flexShrink: '0', lineHeight: '1.5' }">{{ v(msg).icon }}</span>
<span v-if="msg.title" style="flex:1;font-size:0.875rem;font-weight:700;color:#f2f6ff;line-height:1.5">{{ msg.title }}</span>
<div v-else style="flex:1;font-size:0.8125rem;line-height:1.6;color:#94a3b8">{{ msg.body }}</div>
<button
v-if="msg.dismissible"
@click="handleDismiss(msg.id)"
aria-label="Dismiss"
style="flex-shrink:0;background:none;border:none;color:#4a4a4a;cursor:pointer;font-size:1.25rem;line-height:1;padding:0.125rem 0.25rem;border-radius:0.25rem;margin-left:auto"
>×</button>
</div>
<div
v-if="msg.title"
style="padding:0 1rem 0.875rem 2.625rem;font-size:0.8125rem;line-height:1.6;color:#94a3b8"
>{{ msg.body }}</div>
</div>
</template>
</div>
</div>
</template><script>
const variantStyles = {
info: {
color: "#38bdf8",
bg: "rgba(56,189,248,0.06)",
border: "rgba(56,189,248,0.12)",
icon: "\u2139",
},
success: {
color: "#22c55e",
bg: "rgba(34,197,94,0.06)",
border: "rgba(34,197,94,0.12)",
icon: "\u2713",
},
warning: {
color: "#f59e0b",
bg: "rgba(245,158,11,0.06)",
border: "rgba(245,158,11,0.12)",
icon: "\u26A0",
},
danger: {
color: "#ef4444",
bg: "rgba(239,68,68,0.06)",
border: "rgba(239,68,68,0.12)",
icon: "\u2717",
},
};
let messages = [
{
id: 1,
variant: "info",
title: "New Feature Available",
body: "We just launched dark mode support across all components. Toggle it from the settings panel or use the keyboard shortcut Ctrl+D.",
dismissible: true,
},
{
id: 2,
variant: "success",
title: "Deployment Complete",
body: "Your application has been successfully deployed to production. All health checks passed and the CDN cache has been purged.",
dismissible: true,
},
{
id: 3,
variant: "warning",
title: "API Rate Limit",
body: "You have used 85% of your API quota for this billing period. Consider upgrading your plan to avoid service interruptions.",
dismissible: true,
},
{
id: 4,
variant: "danger",
title: "Build Failed",
body: "The production build failed due to a type error in src/components/Dashboard.tsx. Check the build logs for details.",
dismissible: true,
},
{
id: 5,
variant: "info",
title: "",
body: "This is a compact info message without a separate title. Dismiss it with the button on the right.",
dismissible: true,
},
{
id: 6,
variant: "success",
title: "",
body: "This success message cannot be dismissed. It stays visible permanently.",
dismissible: false,
},
];
let dismissing = {};
let dismissed = {};
function handleDismiss(id) {
dismissing[id] = true;
dismissing = dismissing;
setTimeout(() => {
dismissed[id] = true;
dismissed = dismissed;
}, 300);
}
function v(msg) {
return variantStyles[msg.variant];
}
</script>
<div style="min-height:100vh;display:flex;align-items:center;justify-content:center;background:#0a0a0a;font-family:Inter,system-ui,sans-serif;color:#f2f6ff;padding:2rem">
<div style="width:100%;max-width:600px;display:flex;flex-direction:column;gap:0.75rem">
<h1 style="font-size:1.5rem;font-weight:800;margin-bottom:0.375rem">Message Block</h1>
<p style="color:#475569;font-size:0.875rem;margin-bottom:1rem">Colored message blocks with dismiss animation.</p>
{#each messages as msg (msg.id)}
{#if !dismissed[msg.id]}
<div
role="alert"
style="background:{v(msg).bg};border:1px solid {v(msg).border};border-left:4px solid {v(msg).color};border-radius:0.75rem;overflow:hidden;transition:opacity 0.3s ease, transform 0.3s ease, max-height 0.3s ease;opacity:{dismissing[msg.id] ? 0 : 1};transform:{dismissing[msg.id] ? 'translateX(12px) scale(0.98)' : 'none'};max-height:{dismissing[msg.id] ? '0px' : '500px'}"
>
<div style="display:flex;align-items:flex-start;gap:0.625rem;padding:0.875rem 1rem">
<span style="font-size:1rem;color:{v(msg).color};flex-shrink:0;line-height:1.5">{v(msg).icon}</span>
{#if msg.title}
<span style="flex:1;font-size:0.875rem;font-weight:700;color:#f2f6ff;line-height:1.5">{msg.title}</span>
{:else}
<div style="flex:1;font-size:0.8125rem;line-height:1.6;color:#94a3b8">{msg.body}</div>
{/if}
{#if msg.dismissible}
<button
on:click={() => handleDismiss(msg.id)}
aria-label="Dismiss"
style="flex-shrink:0;background:none;border:none;color:#4a4a4a;cursor:pointer;font-size:1.25rem;line-height:1;padding:0.125rem 0.25rem;border-radius:0.25rem;margin-left:auto"
>×</button>
{/if}
</div>
{#if msg.title}
<div style="padding:0 1rem 0.875rem 2.625rem;font-size:0.8125rem;line-height:1.6;color:#94a3b8">
{msg.body}
</div>
{/if}
</div>
{/if}
{/each}
</div>
</div>Message Block
Colored message blocks with optional header, body content, and a dismiss button that removes the message with a smooth animation.
Variants
| Variant | Color | Use case |
|---|---|---|
info | Blue | Informational tips, announcements |
success | Green | Completed actions, confirmations |
warning | Amber | Caution, important notices |
danger | Red | Errors, critical issues |
Features
- Four color variants
- Optional title header
- Dismiss button with fade-out + collapse animation
- Accessible markup
- Dark theme styling