
2025/06/20
8分で読む
Liquid Glass React活用事例:実践的なUI/UXデザインパターン
Liquid Glass効果を活用したReactアプリケーションの実践的な使用例。ナビゲーション、モーダル、カード、フォームなどの具体的な実装例を紹介します。
Liquid Glass効果をReactアプリケーションで効果的に活用するための実践的な使用例を紹介します。実際のプロジェクトで使える具体的なコンポーネントとデザインパターンを詳しく解説します。
ナビゲーション系コンポーネント
固定ヘッダーナビゲーション
スクロール連動型ナビゲーション:
import React, { useState, useEffect } from 'react';
import { LiquidGlass } from './LiquidGlass';
const ScrollAwareNavbar = ({ items, logo }) => {
const [scrolled, setScrolled] = useState(false);
const [visible, setVisible] = useState(true);
const [lastScrollY, setLastScrollY] = useState(0);
useEffect(() => {
const handleScroll = () => {
const currentScrollY = window.scrollY;
// スクロール状態の更新
setScrolled(currentScrollY > 50);
// ナビゲーションの表示/非表示制御
if (currentScrollY > lastScrollY && currentScrollY > 100) {
setVisible(false); // 下スクロール時は非表示
} else {
setVisible(true); // 上スクロール時は表示
}
setLastScrollY(currentScrollY);
};
window.addEventListener('scroll', handleScroll, { passive: true });
return () => window.removeEventListener('scroll', handleScroll);
}, [lastScrollY]);
return (
<nav
className={`
fixed top-0 left-0 right-0 z-50 transition-all duration-300
${visible ? 'translate-y-0' : '-translate-y-full'}
`}
>
<LiquidGlass
intensity={scrolled ? 'strong' : 'medium'}
className="navbar-container"
>
<div className="flex items-center justify-between px-6 py-4">
<div className="navbar-logo">
{logo}
</div>
<ul className="flex space-x-8">
{items.map((item, index) => (
<li key={index}>
<a
href={item.href}
className="navbar-link hover:text-blue-600 transition-colors"
>
{item.label}
</a>
</li>
))}
</ul>
</div>
</LiquidGlass>
</nav>
);
};
サイドバーナビゲーション
展開可能なサイドバー:
const LiquidGlassSidebar = ({ isOpen, onToggle, menuItems }) => {
return (
<>
{/* オーバーレイ */}
{isOpen && (
<div
className="fixed inset-0 bg-black bg-opacity-30 z-40"
onClick={onToggle}
/>
)}
{/* サイドバー */}
<div
className={`
fixed left-0 top-0 h-full w-80 z-50 transform transition-transform duration-300
${isOpen ? 'translate-x-0' : '-translate-x-full'}
`}
>
<LiquidGlass intensity="strong" className="h-full">
<div className="p-6">
<div className="flex items-center justify-between mb-8">
<h2 className="text-xl font-semibold">メニュー</h2>
<button
onClick={onToggle}
className="p-2 hover:bg-white hover:bg-opacity-20 rounded-lg"
>
✕
</button>
</div>
<nav>
<ul className="space-y-2">
{menuItems.map((item, index) => (
<li key={index}>
<a
href={item.href}
className="flex items-center p-3 rounded-lg hover:bg-white hover:bg-opacity-20 transition-colors"
>
<span className="mr-3">{item.icon}</span>
{item.label}
</a>
</li>
))}
</ul>
</nav>
</div>
</LiquidGlass>
</div>
</>
);
};
モーダル・ダイアログ系
多段階モーダル
ステップ形式のモーダル:
const MultiStepModal = ({ isOpen, onClose, steps }) => {
const [currentStep, setCurrentStep] = useState(0);
const [formData, setFormData] = useState({});
const nextStep = () => {
if (currentStep < steps.length - 1) {
setCurrentStep(currentStep + 1);
}
};
const prevStep = () => {
if (currentStep > 0) {
setCurrentStep(currentStep - 1);
}
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
{/* 背景オーバーレイ */}
<div
className="absolute inset-0 bg-black bg-opacity-40"
onClick={onClose}
/>
{/* モーダルコンテンツ */}
<LiquidGlass
intensity="strong"
className="relative w-full max-w-2xl max-h-[90vh] overflow-hidden"
>
{/* プログレスバー */}
<div className="p-6 border-b border-white border-opacity-20">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-semibold">
{steps[currentStep].title}
</h2>
<button
onClick={onClose}
className="p-2 hover:bg-white hover:bg-opacity-20 rounded-lg"
>
✕
</button>
</div>
<div className="w-full bg-white bg-opacity-20 rounded-full h-2">
<div
className="bg-blue-500 h-2 rounded-full transition-all duration-300"
style={{ width: `${((currentStep + 1) / steps.length) * 100}%` }}
/>
</div>
</div>
{/* ステップコンテンツ */}
<div className="p-6 overflow-y-auto">
{steps[currentStep].content}
</div>
{/* ナビゲーションボタン */}
<div className="p-6 border-t border-white border-opacity-20 flex justify-between">
<button
onClick={prevStep}
disabled={currentStep === 0}
className="px-4 py-2 bg-white bg-opacity-20 rounded-lg disabled:opacity-50"
>
戻る
</button>
<button
onClick={currentStep === steps.length - 1 ? onClose : nextStep}
className="px-4 py-2 bg-blue-500 text-white rounded-lg"
>
{currentStep === steps.length - 1 ? '完了' : '次へ'}
</button>
</div>
</LiquidGlass>
</div>
);
};
確認ダイアログ
アクション確認用ダイアログ:
const ConfirmationDialog = ({
isOpen,
onClose,
onConfirm,
title,
message,
type = 'default'
}) => {
const getTypeStyles = (type) => {
const styles = {
default: 'border-blue-500 text-blue-600',
danger: 'border-red-500 text-red-600',
warning: 'border-yellow-500 text-yellow-600',
success: 'border-green-500 text-green-600'
};
return styles[type] || styles.default;
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div
className="absolute inset-0 bg-black bg-opacity-40"
onClick={onClose}
/>
<LiquidGlass
intensity="strong"
className="relative w-full max-w-md"
>
<div className="p-6">
<div className={`w-12 h-12 rounded-full border-2 ${getTypeStyles(type)} flex items-center justify-center mb-4`}>
{type === 'danger' && '⚠️'}
{type === 'warning' && '⚠️'}
{type === 'success' && '✅'}
{type === 'default' && 'ℹ️'}
</div>
<h3 className="text-lg font-semibold mb-2">{title}</h3>
<p className="text-gray-600 mb-6">{message}</p>
<div className="flex space-x-3">
<button
onClick={onClose}
className="flex-1 px-4 py-2 bg-white bg-opacity-20 rounded-lg hover:bg-opacity-30 transition-colors"
>
キャンセル
</button>
<button
onClick={() => {
onConfirm();
onClose();
}}
className={`flex-1 px-4 py-2 rounded-lg text-white ${
type === 'danger' ? 'bg-red-500 hover:bg-red-600' : 'bg-blue-500 hover:bg-blue-600'
} transition-colors`}
>
確認
</button>
</div>
</div>
</LiquidGlass>
</div>
);
};
カード・コンテンツ系
インタラクティブカードグリッド
ホバー効果付きカードレイアウト:
const InteractiveCardGrid = ({ items }) => {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 p-6">
{items.map((item, index) => (
<InteractiveCard key={index} {...item} />
))}
</div>
);
};
const InteractiveCard = ({ title, description, image, tags, onClick }) => {
const [isHovered, setIsHovered] = useState(false);
return (
<LiquidGlass
intensity={isHovered ? 'strong' : 'medium'}
className="group cursor-pointer transform transition-all duration-300 hover:scale-105"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
onClick={onClick}
>
{/* 画像部分 */}
{image && (
<div className="relative overflow-hidden rounded-t-lg">
<img
src={image}
alt={title}
className="w-full h-48 object-cover transition-transform duration-300 group-hover:scale-110"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black from-opacity-50 to-transparent" />
</div>
)}
{/* コンテンツ部分 */}
<div className="p-6">
<h3 className="text-xl font-semibold mb-2 group-hover:text-blue-600 transition-colors">
{title}
</h3>
<p className="text-gray-600 mb-4 line-clamp-3">
{description}
</p>
{/* タグ */}
{tags && (
<div className="flex flex-wrap gap-2">
{tags.map((tag, index) => (
<span
key={index}
className="px-2 py-1 bg-white bg-opacity-20 rounded-full text-sm"
>
{tag}
</span>
))}
</div>
)}
</div>
</LiquidGlass>
);
};
展開可能なアコーディオン
FAQ形式のアコーディオン:
const LiquidGlassAccordion = ({ items }) => {
const [openItems, setOpenItems] = useState(new Set());
const toggleItem = (index) => {
const newOpenItems = new Set(openItems);
if (newOpenItems.has(index)) {
newOpenItems.delete(index);
} else {
newOpenItems.add(index);
}
setOpenItems(newOpenItems);
};
return (
<div className="space-y-4">
{items.map((item, index) => (
<AccordionItem
key={index}
index={index}
item={item}
isOpen={openItems.has(index)}
onToggle={() => toggleItem(index)}
/>
))}
</div>
);
};
const AccordionItem = ({ index, item, isOpen, onToggle }) => {
return (
<LiquidGlass
intensity="medium"
className="overflow-hidden transition-all duration-300"
>
{/* ヘッダー */}
<button
onClick={onToggle}
className="w-full p-6 text-left flex items-center justify-between hover:bg-white hover:bg-opacity-10 transition-colors"
>
<h3 className="text-lg font-semibold">{item.question}</h3>
<span
className={`transform transition-transform duration-300 ${
isOpen ? 'rotate-180' : 'rotate-0'
}`}
>
▼
</span>
</button>
{/* コンテンツ */}
<div
className={`overflow-hidden transition-all duration-300 ${
isOpen ? 'max-h-96 opacity-100' : 'max-h-0 opacity-0'
}`}
>
<div className="px-6 pb-6 border-t border-white border-opacity-20">
<p className="text-gray-600 mt-4">{item.answer}</p>
</div>
</div>
</LiquidGlass>
);
};
フォーム系コンポーネント
多段階フォーム
ステップ形式の入力フォーム:
const MultiStepForm = ({ onSubmit }) => {
const [currentStep, setCurrentStep] = useState(0);
const [formData, setFormData] = useState({
personal: {},
contact: {},
preferences: {}
});
const steps = [
{
title: '個人情報',
component: PersonalInfoStep,
key: 'personal'
},
{
title: '連絡先',
component: ContactInfoStep,
key: 'contact'
},
{
title: '設定',
component: PreferencesStep,
key: 'preferences'
}
];
const updateFormData = (stepKey, data) => {
setFormData(prev => ({
...prev,
[stepKey]: { ...prev[stepKey], ...data }
}));
};
const nextStep = () => {
if (currentStep < steps.length - 1) {
setCurrentStep(currentStep + 1);
} else {
onSubmit(formData);
}
};
const prevStep = () => {
if (currentStep > 0) {
setCurrentStep(currentStep - 1);
}
};
const CurrentStepComponent = steps[currentStep].component;
return (
<div className="max-w-2xl mx-auto p-6">
<LiquidGlass intensity="medium" className="overflow-hidden">
{/* プログレスヘッダー */}
<div className="p-6 border-b border-white border-opacity-20">
<div className="flex items-center justify-between mb-4">
{steps.map((step, index) => (
<div
key={index}
className={`flex items-center ${
index <= currentStep ? 'text-blue-600' : 'text-gray-400'
}`}
>
<div
className={`w-8 h-8 rounded-full flex items-center justify-center border-2 ${
index <= currentStep
? 'border-blue-600 bg-blue-600 text-white'
: 'border-gray-400'
}`}
>
{index < currentStep ? '✓' : index + 1}
</div>
{index < steps.length - 1 && (
<div
className={`w-16 h-0.5 mx-2 ${
index < currentStep ? 'bg-blue-600' : 'bg-gray-400'
}`}
/>
)}
</div>
))}
</div>
<h2 className="text-xl font-semibold">{steps[currentStep].title}</h2>
</div>
{/* フォームコンテンツ */}
<div className="p-6">
<CurrentStepComponent
data={formData[steps[currentStep].key]}
onChange={(data) => updateFormData(steps[currentStep].key, data)}
/>
</div>
{/* ナビゲーション */}
<div className="p-6 border-t border-white border-opacity-20 flex justify-between">
<button
onClick={prevStep}
disabled={currentStep === 0}
className="px-6 py-2 bg-white bg-opacity-20 rounded-lg disabled:opacity-50 hover:bg-opacity-30 transition-colors"
>
戻る
</button>
<button
onClick={nextStep}
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
>
{currentStep === steps.length - 1 ? '送信' : '次へ'}
</button>
</div>
</LiquidGlass>
</div>
);
};
インライン編集フォーム
その場編集可能なフォーム:
const InlineEditForm = ({ initialData, onSave }) => {
const [isEditing, setIsEditing] = useState(false);
const [formData, setFormData] = useState(initialData);
const [hasChanges, setHasChanges] = useState(false);
const handleChange = (field, value) => {
setFormData(prev => ({ ...prev, [field]: value }));
setHasChanges(true);
};
const handleSave = async () => {
await onSave(formData);
setIsEditing(false);
setHasChanges(false);
};
const handleCancel = () => {
setFormData(initialData);
setIsEditing(false);
setHasChanges(false);
};
return (
<LiquidGlass
intensity={isEditing ? 'strong' : 'medium'}
className="transition-all duration-300"
>
<div className="p-6">
<div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-semibold">プロフィール情報</h3>
{!isEditing ? (
<button
onClick={() => setIsEditing(true)}
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
>
編集
</button>
) : (
<div className="flex space-x-2">
<button
onClick={handleCancel}
className="px-4 py-2 bg-white bg-opacity-20 rounded-lg hover:bg-opacity-30 transition-colors"
>
キャンセル
</button>
<button
onClick={handleSave}
disabled={!hasChanges}
className="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 disabled:opacity-50 transition-colors"
>
保存
</button>
</div>
)}
</div>
<div className="space-y-4">
{Object.entries(formData).map(([key, value]) => (
<div key={key} className="flex items-center space-x-4">
<label className="w-24 text-sm font-medium capitalize">
{key}:
</label>
{isEditing ? (
<input
type="text"
value={value}
onChange={(e) => handleChange(key, e.target.value)}
className="flex-1 px-3 py-2 bg-white bg-opacity-20 rounded-lg border border-white border-opacity-30 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
) : (
<span className="flex-1 py-2">{value}</span>
)}
</div>
))}
</div>
</div>
</LiquidGlass>
);
};
データ表示系
統計ダッシュボード
メトリクス表示カード:
const MetricsCard = ({ title, value, change, trend, icon }) => {
const getTrendColor = (trend) => {
return trend === 'up' ? 'text-green-500' :
trend === 'down' ? 'text-red-500' : 'text-gray-500';
};
return (
<LiquidGlass intensity="medium" className="p-6">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-3">
<div className="p-2 bg-blue-500 bg-opacity-20 rounded-lg">
{icon}
</div>
<h3 className="text-sm font-medium text-gray-600">{title}</h3>
</div>
<span className={`text-sm ${getTrendColor(trend)}`}>
{trend === 'up' ? '↗' : trend === 'down' ? '↘' : '→'} {change}
</span>
</div>
<div className="text-2xl font-bold mb-2">{value}</div>
<div className="w-full bg-white bg-opacity-20 rounded-full h-2">
<div
className="bg-blue-500 h-2 rounded-full transition-all duration-1000"
style={{ width: `${Math.random() * 100}%` }}
/>
</div>
</LiquidGlass>
);
};
const Dashboard = ({ metrics }) => {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 p-6">
{metrics.map((metric, index) => (
<MetricsCard key={index} {...metric} />
))}
</div>
);
};
結論
これらの実用的な使用例は、Liquid Glass効果をReactアプリケーションで効果的に活用する方法を示しています。
重要なポイント:
- 適切な強度設定: 用途に応じた効果レベルの選択
- パフォーマンス考慮: 必要以上の効果を避ける
- アクセシビリティ: 動きの削減オプションの提供
- ユーザビリティ: 美しさと使いやすさのバランス
これらのコンポーネントを参考に、あなたのプロジェクトに最適なLiquid Glass UIを実装してください。
完全なコンポーネントライブラリとサンプルコードについては、GitHubリポジトリで確認してください。
その他の投稿

ニュース
macOS Tahoe互換性ガイド:Intel Macサポート終了
macOS Tahoe互換性の完全ガイド。お使いのMacがサポートされているか、どの機能が利用可能かを確認してください。


製品
Mac用Phone App完全ガイド:iPhoneなしで通話する方法
macOS TahoeのPhone Appの設定、使用方法、トラブルシューティングの完全ガイド。Macで直接通話を発信・受信する方法を学びましょう。


製品
M4 Mac ゲーミングパフォーマンスレビュー:macOS Tahoeで進化するゲーム体験
M4チップ搭載MacでのゲーミングパフォーマンスとmacOS TahoeのGaming Modeを詳細レビュー。フレームレート、互換性、最適化設定を徹底検証します。

ニュースレター
コミュニティに参加
最新ニュースとアップデートをお届けするニュースレターに登録しましょう