Dynamic Island
Thông báo bung ra từ một viên đen nhỏ rồi thu gọn vào lại, mượt như trên iPhone
.zip · HTML / CSS / JS · v1.0.0
Miễn phí — không cần tài khoản hay email. Luôn là bản mới nhất.
Đang chạy trực tiếp
Giới thiệu
Mọi thông báo trong ứng dụng, gói gọn trong một viên đen nhỏ. Nó nằm ở đỉnh màn hình, và khi có chuyện cần nói thì bung ra vừa khít kích thước tin nhắn — rồi thu về như cũ. Lưu xong thì hiện dấu tích tự vẽ nét; thanh toán bắt đầu bằng vòng xoay rồi biến thành biên nhận; tải file thì thanh tiến độ đầy dần tại chỗ; đồng hồ hẹn giờ lặng lẽ đếm trong đảo thu gọn và mở ra bảng điều khiển khi bạn rê chuột hay chạm vào.
- Thông báo, chờ → kết quả, tiến độ, nút bấm và live activity, tất cả chỉ từ một lệnh
show() - Hiệu ứng biến hình chạy theo lò xo thật, giải một lần rồi giao cho CSS dưới dạng easing
linear()— mượt ở mọi tần số quét, không có JavaScript nào chạy trong lúc chuyển động - Hàng đợi biết cư xử: chỉ một hòn đảo, thông báo lần lượt lên sân khấu, live activity nhường chỗ cho thông báo mới rồi quay lại sau
- Mọi màu sắc, kích thước và cả chiếc lò xo đều là CSS custom property; sáng tối theo trang của bạn
- Không cần cài gì: một file CSS và một script nhỏ, hoặc một component React
Tính năng · 11
- Một hòn đảo duy nhất biến hình — rộng, cao, bo góc và nội dung — theo lò xo thật
- Thông báo bốn sắc thái, biểu tượng tự vẽ nét hoặc nảy lên khi xuất hiện
- promise(): vòng xoay chờ biến thành kết quả, thành công hay lỗi
- Tiến độ dạng vòng quanh biểu tượng hoặc thanh ngang, cập nhật qua handle mà show() trả về
- Nút bấm ngay trên đảo, ví dụ Hoàn tác, mỗi nút một callback
- Live activity (đồng hồ hẹn giờ, bài hát đang phát) mở rộng khi rê chuột, focus hoặc chạm
- Hàng đợi thông minh: thông báo lần lượt xuất hiện, live activity nhường chỗ rồi quay lại
- Dừng đếm giờ khi đang rê chuột, đang focus hoặc tab bị ẩn; vuốt hoặc Esc để tắt
- Đặt ở đỉnh hoặc đáy màn hình; nút data-island không cần viết code
- Bản HTML/CSS/JS thuần và bản React (dùng được với Next.js)
- Không phụ thuộc thư viện, dưới 10 KB sau gzip (cả script lẫn CSS)
Cách dùng
Gắn stylesheet và script một lần. Hòn đảo tự dựng lên vào lần đầu tiên có chuyện cần nói:
<link rel="stylesheet" href="dynamic-island.css" />
<script src="dynamic-island.js" defer></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
DynamicIsland.show({ title: "Saved", description: "All changes are in", tone: "success" });
});
</script>Không cần code: phần tử nào có data-island thì bấm vào sẽ hiện thông báo của nó.
<button type="button" data-island data-island-title="Đã chép link" data-island-tone="success"
data-island-action="Hoàn tác" data-island-action-title="Đã hoàn tác">
Chép link
</button>Các attribute: data-island-title (thiếu thì lấy chữ của phần tử), -description, -tone,
-icon (chữ, ví dụ một emoji), -trailing, -duration (ms, 0 là giữ tới khi tắt), -id,
-action (nhãn của nút) và -action-title (hiện ra sau khi bấm nút). Phần tử thêm vào sau vẫn
hoạt động; DynamicIsland.init(container) để lắng nghe trong một vùng gốc khác.
Đủ mọi kiểu
// Thông báo: neutral, success, error, info hoặc warning
DynamicIsland.show({ title: "Thẻ bị từ chối", description: "Thử thẻ khác nhé", tone: "error" });
// Đang chờ → kết quả
DynamicIsland.promise(pay(), {
loading: "Đang thanh toán…",
success: (receipt) => ({ title: "Đã thanh toán", description: receipt.total }),
error: (error) => ({ title: "Thanh toán thất bại", description: error.message }),
});
// Tiến độ: vòng quanh biểu tượng, hoặc thanh ngang
const upload = DynamicIsland.show({ title: "Đang tải lên", progress: 0, progressStyle: "bar", trailing: "0%" });
upload.update({ progress: 0.6, trailing: "60%" });
upload.update({ title: "Đã tải lên", tone: "success", progress: null, trailing: undefined });
// Nút bấm
DynamicIsland.show({
title: "Đã xoá tin nhắn",
actions: [{ label: "Hoàn tác", variant: "primary", onClick: () => restore() }],
});
// Live activity: thu gọn cho tới khi rê chuột, focus hoặc chạm
const timer = DynamicIsland.show({
title: "Hẹn giờ", trailing: "4:59", description: "Luộc mì", expandable: true, accent: "#ff9f0a",
actions: [{ label: "Tạm dừng", dismiss: false, onClick: (handle) => handle.update({ description: "Đang tạm dừng" }) }],
});show() trả về { id, update(changes), dismiss() }. Ngoài ra còn: DynamicIsland.update(id, changes),
dismiss(id?) (không truyền id thì tắt cái đang hiện), clear() và configure(options).
Thông báo
| Trường | Mặc định | Tác dụng |
|---|---|---|
title |
— | Dòng tiêu đề (bắt buộc) |
description |
— | Dòng thứ hai; với live activity thì hiện khi mở rộng |
tone |
"neutral" |
success, error, info, warning: biểu tượng và màu |
icon |
theo tone | Chữ (emoji) hoặc một DOM node (được nhân bản); với React là React node bất kỳ |
loading |
false |
Vòng xoay thay cho biểu tượng |
progress |
null |
Từ 0 tới 1; progressStyle: "ring" (mặc định) hoặc "bar" |
trailing |
— | Chữ ngắn ở đầu bên kia: "4:59", "42%" |
actions |
— | { label, onClick(handle), variant: "primary", dismiss: false } |
expandable |
false |
Live activity: thu gọn cho tới khi rê chuột, focus hoặc chạm |
accent |
theo tone | Màu cho biểu tượng, tiến độ và nút chính |
duration |
4 giây, 6 giây với lỗi và thông báo có nút | ms; Infinity hoặc 0 là giữ nguyên. Đang chờ, tiến độ và live activity mặc định giữ nguyên |
dismissible |
true |
Có cho vuốt và Esc tắt hay không |
id |
tự sinh | Gọi show với cùng id sẽ cập nhật thông báo đó |
onDismiss(reason) |
— | "timeout", "swipe", "escape", "action" or "api" |
Chỉ có một hòn đảo nên các thông báo lần lượt chờ tới lượt: thông báo thường hiện nối tiếp nhau và không bao giờ bị cắt ngang; thông báo giữ lâu (live activity, đang tải lên) nhường chỗ cho thông báo mới rồi quay lại sau. Đồng hồ đếm ngược dừng khi đang rê chuột hoặc focus vào đảo, và khi tab bị ẩn. Vuốt về phía mép màn hình, hoặc bấm Esc khi đang rê chuột hay focus, để tắt.
Tuỳ chọn
DynamicIsland.configure({ … }), gọi lúc nào cũng được:
| Tuỳ chọn | Mặc định | Tác dụng |
|---|---|---|
position |
"top" |
"top" (đỉnh) hoặc "bottom" (đáy) màn hình |
label |
"Notifications" |
Tên landmark cho trình đọc màn hình |
container |
document.body |
Nơi gắn hòn đảo vào; đặt trong phần tử khác (nhớ cho nó position: relative) thì đảo nằm gọn trong phần tử đó, ví dụ một khung điện thoại |
spring |
theo stylesheet | { stiffness, damping, mass } cho hiệu ứng biến hình, được đổi thành easing linear() |
Chép DynamicIsland.tsx, dynamic-island.css và thư mục core vào dự án. Gắn hòn đảo một lần,
rồi hiện thông báo từ bất kỳ đâu:
import { DynamicIsland, island, useIsland } from "./DynamicIsland";
export default function Layout({ children }) {
return (
<>
{children}
<DynamicIsland position="top" />
</>
);
}
function SaveButton() {
const { show, promise } = useIsland();
return <button onClick={() => promise(save(), { loading: "Đang lưu…", success: "Đã lưu", error: "Chưa lưu được" })}>Lưu</button>;
}
// Cả bên ngoài component: trong store, API client…
island.show({ title: "Đang phát", icon: <AlbumArt />, expandable: true, trailing: "2:14" });<DynamicIsland /> nhận position, label và spring, và là nơi React vẽ các biểu tượng React
(biểu tượng dạng chữ thì không cần nó). Thông báo y như trên; icon là React node bất kỳ. React 18 hoặc 19.
Component bắt đầu bằng "use client": đặt <DynamicIsland /> trong app/layout.tsx rồi gọi
island.show() từ bất kỳ client component nào. Với Pages Router, đặt nó trong pages/_app và
chuyển dòng import CSS vào đó.
Đặt các biến sau trên .dynamic-island (hoặc class của bạn bọc ngoài hòn đảo):
| Biến | Đổi gì |
|---|---|
--island-surface, --island-text, --island-muted |
Viên đảo và chữ trên đó |
--island-chip, --island-focus |
Nền nút và rãnh tiến độ, viền focus |
--island-edge, --island-shadow |
Viền mảnh và bóng đổ quanh đảo |
--island-neutral, --island-success, --island-error, --island-info, --island-warning |
Màu các sắc thái |
--island-rest-width, --island-rest-height |
Kích thước khi thu về nghỉ |
--island-max-width, --island-radius, --island-icon-size |
Hình dạng và kích thước thông báo |
--island-offset, --island-z-index |
Khoảng cách tới mép, thứ tự lớp |
--island-spring, --island-spring-duration |
Hiệu ứng biến hình (easing nào cũng được) |
--island-fade |
Hiệu ứng mờ: hiện, ẩn, giảm chuyển động |
Màu nào khác nhau giữa sáng và tối thì dùng light-dark(): tự theo color-scheme của trang.
Hỏi về sản phẩm này
Chưa chắc có hợp dự án của bạn? Cứ hỏi — câu trả lời lấy từ chính trang này.
Lịch sử phiên bản
1.0.0First releaseMới nhất
Plain HTML/CSS/JS and React builds of a spring-morphing island: toasts, promise and progress states, actions, expandable live activities, a smart queue, swipe and Esc to dismiss, and full screen-reader and reduced-motion support.
- Bản sửa lỗi và tính năng mới đều có ngay ở nút tải này — file tải về luôn là bản mới nhất.

