البطاقات والنوافذ المنبثقة وأنماط واجهة المستخدم الشائعة
لماذا تهم أنماط واجهة المستخدم
تبنى واجهات الويب الحديثة من مجموعة أدوات من الأنماط المرئية القابلة لإعادة الاستخدام: البطاقات التي تجمع المحتوى المرتبط، والنوافذ المنبثقة التي تتطلب انتباها مركزا، والتلميحات التي توفر مساعدة سياقية، والأكورديونات التي تنظم المعلومات الكثيفة، ومؤشرات التقدم التي تنقل الحالة. تظهر هذه الأنماط في كل موقع وتطبيق تستخدمه تقريبا. فهم كيفية بنائها بـ HTML و CSS خالص -- بدون الاعتماد على إطار عمل أو مكتبة JavaScript -- يمنحك تحكما كاملا في التصميم والأداء وإمكانية الوصول.
في هذا الدرس، ستتقن تشريح وبناء أهم أنماط واجهة المستخدم في CSS. ستبني بطاقات مع تأثيرات تمرير وتراكبات صور وتخطيطات متجاوبة؛ وتنشئ نوافذ منبثقة باستخدام كل من تقنية CSS :target وعنصر <dialog> الأصلي؛ وتنسق إشعارات التوست والتلميحات والأكورديونات وأشرطة التقدم ومكونات الصورة الرمزية وشاشات التحميل الهيكلية. كل نمط مبني بـ HTML دلالي ومنسق بـ CSS ومصمم مع مراعاة إمكانية الوصول.
تشريح البطاقة
البطاقة هي وحدة محتوى مستقلة تجمع المعلومات المرتبطة في كتلة بصرية واحدة. البطاقة النموذجية لها خمس مناطق مميزة: منطقة صورة في الأعلى، ورأس بعنوان، وجسم بنص وصفي، وبيانات وصفية أو علامات اختيارية، وتذييل بأزرار إجراء أو روابط. ليست كل بطاقة تحتاج جميع المناطق الخمس -- يجب أن تضمن فقط الأجزاء التي تخدم محتواك.
بنية البطاقة الأساسية
<!-- بنية HTML -->
<article class="card">
<div class="card__image">
<img src="/images/project.jpg" alt="لقطة شاشة مشروع لوحة التحكم" loading="lazy">
</div>
<div class="card__header">
<h3 class="card__title">إعادة تصميم لوحة التحكم</h3>
<span class="card__meta">نشر في 15 يناير 2025</span>
</div>
<div class="card__body">
<p>إعادة تصميم كاملة للوحة التحليلات مع تحسين عرض البيانات والتخطيطات المتجاوبة.</p>
</div>
<div class="card__footer">
<a href="#" class="card__action">عرض المشروع</a>
<a href="#" class="card__action card__action--secondary">الكود المصدري</a>
</div>
</article>
/* CSS */
.card {
background: var(--bg-white);
border: 1px solid var(--border-light);
border-radius: 8px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.card__image img {
width: 100%;
height: 200px;
object-fit: cover;
display: block;
}
.card__header {
padding: 1.25rem 1.25rem 0;
}
.card__title {
margin: 0 0 0.25rem;
font-size: 1.15rem;
color: var(--text-dark);
}
.card__meta {
font-size: 0.8rem;
color: var(--text-light);
}
.card__body {
padding: 0.75rem 1.25rem;
flex: 1;
color: var(--text-dark);
font-size: 0.95rem;
line-height: 1.6;
}
.card__footer {
padding: 0 1.25rem 1.25rem;
display: flex;
gap: 0.75rem;
}
.card__action {
display: inline-block;
padding: 0.5rem 1rem;
text-decoration: none;
font-size: 0.85rem;
font-weight: 600;
border-radius: 4px;
color: var(--bg-white);
background-color: var(--primary);
transition: background-color 0.2s;
}
.card__action:hover {
opacity: 0.9;
}
.card__action--secondary {
background-color: transparent;
color: var(--primary);
border: 1px solid var(--primary);
}
.card__action--secondary:hover {
background-color: var(--primary-light);
}
<article> للبطاقات مناسب دلاليا عندما تمثل كل بطاقة قطعة محتوى مستقلة يمكن أن تقف بذاتها (مثل مقال مدونة أو منتج أو مشروع). إذا كانت البطاقة زخرفية بحتة أو جزء من وحدة محتوى أكبر، فإن <div> أكثر ملاءمة. خاصيتا flex-direction: column وflex: 1 على الجسم تضمنان أن البطاقة تمتد لملء ارتفاع متساوٍ عندما توضع البطاقات في شبكة، مما يدفع التذييل إلى الأسفل.تأثيرات تمرير البطاقة
توفر تأثيرات التمرير ملاحظات بصرية بأن البطاقة تفاعلية. تشمل التأثيرات الشائعة تغييرات طفيفة في الارتفاع وتحولات لون الحدود والانتقال لأعلى. المفتاح هو الدقة -- يجب أن يشعر التأثير بالطبيعية وليس المفاجأة. ضمّن دائما transition لتنعيم الحركة واستخدم :focus-within إلى جانب :hover حتى يحصل مستخدمو لوحة المفاتيح على نفس الملاحظات البصرية.
تأثيرات التمرير والتركيز للبطاقة
/* تأثير الرفع مع الظل */
.card {
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.card:hover,
.card:focus-within {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
/* تغيير لون الحدود */
.card--bordered {
border: 2px solid var(--border-light);
transition: border-color 0.2s;
}
.card--bordered:hover,
.card--bordered:focus-within {
border-color: var(--primary);
}
/* تكبير الصورة عند التمرير */
.card__image {
overflow: hidden;
}
.card__image img {
transition: transform 0.3s ease;
}
.card:hover .card__image img,
.card:focus-within .card__image img {
transform: scale(1.05);
}
/* مجمع: رفع + تكبير صورة + ظل */
.card--interactive {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card--interactive:hover,
.card--interactive:focus-within {
transform: translateY(-6px);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.1);
}
.card--interactive:hover .card__image img {
transform: scale(1.08);
}
تخطيطات البطاقات مع Flexbox و Grid
لا تعرض البطاقات أبدا تقريبا بمفردها -- تظهر في شبكات. CSS Grid مثالي لتخطيطات البطاقات لأنه يتعامل مع أعمدة متساوية الارتفاع تلقائيا ويتيح لك إنشاء تخطيطات متجاوبة بأقل كود ممكن. مجموعة auto-fill وminmax() تنشئ شبكة متجاوبة بالكامل بدون أي استعلامات وسائط.
تخطيطات شبكة بطاقات متجاوبة
/* شبكة متجاوبة تلقائيا: بدون استعلامات وسائط */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
padding: 1.5rem;
}
/* شبكة ثابتة من 3 أعمدة */
.card-grid--three {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
}
/* بديل Flexbox للبطاقات الملتفة */
.card-row {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
}
.card-row .card {
flex: 1 1 300px;
max-width: calc(33.333% - 1rem);
}
/* تخطيط عمودين للبطاقات الأكبر */
.card-grid--two {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
gap: 2rem;
}
/* بطاقة مميزة: تمتد على عمودين */
.card-grid .card--featured {
grid-column: span 2;
}
@media (max-width: 768px) {
.card-row .card {
max-width: 100%;
}
.card-grid .card--featured {
grid-column: span 1;
}
}
البطاقات الأفقية
تعرض البطاقات الأفقية الصورة جنبا إلى جنب مع المحتوى بدلا من التكديس العمودي. يعمل هذا التخطيط بشكل جيد لمعاينات المقالات ونتائج البحث والمحتوى على شكل قائمة. تستخدم التقنية Flexbox مع flex-direction: row وتعطي الصورة عرضا ثابتا بينما تملأ منطقة المحتوى المساحة المتبقية.
تخطيط البطاقة الأفقية
<!-- بنية HTML -->
<article class="card card--horizontal">
<div class="card__image">
<img src="/images/post.jpg" alt="غلاف المقال">
</div>
<div class="card__content">
<h3 class="card__title">فهم CSS Grid</h3>
<p class="card__body">نظرة عميقة في تخطيط CSS Grid مع أمثلة عملية وأنماط شائعة.</p>
<div class="card__footer">
<span class="card__meta">5 دقائق قراءة</span>
<a href="#">اقرأ المزيد</a>
</div>
</div>
</article>
/* CSS */
.card--horizontal {
flex-direction: row;
}
.card--horizontal .card__image {
flex: 0 0 240px;
}
.card--horizontal .card__image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.card--horizontal .card__content {
flex: 1;
display: flex;
flex-direction: column;
padding: 1.25rem;
}
.card--horizontal .card__body {
flex: 1;
padding: 0;
}
.card--horizontal .card__footer {
padding: 0;
margin-top: 0.75rem;
display: flex;
justify-content: space-between;
align-items: center;
}
/* تكديس عمودي على الشاشات الصغيرة */
@media (max-width: 600px) {
.card--horizontal {
flex-direction: column;
}
.card--horizontal .card__image {
flex: none;
height: 200px;
}
}
البطاقات مع الأشرطة والشارات والتراكبات
تجذب اللمسات البصرية مثل الأشرطة والشارات وتراكبات الصور الانتباه إلى بطاقات محددة. عادة ما تظهر الأشرطة بشكل مائل عبر الزاوية، وتجلس الشارات في زاوية كتسميات صغيرة، وتغطي التراكبات الصورة بنص أو تأثيرات تدرج. كل هذا يتحقق بتحديد المواقع في CSS والعناصر الزائفة.
شارات وتراكبات البطاقة
/* شارة الزاوية */
.card--with-badge {
position: relative;
}
.card__badge {
position: absolute;
top: 1rem;
right: 1rem;
padding: 0.25rem 0.75rem;
background-color: var(--primary);
color: white;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
border-radius: 999px;
z-index: 1;
}
/* تراكب صورة بتدرج */
.card__image--overlay {
position: relative;
}
.card__image--overlay::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 60%;
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
pointer-events: none;
}
.card__image--overlay .card__overlay-text {
position: absolute;
bottom: 1rem;
left: 1rem;
right: 1rem;
color: white;
z-index: 1;
font-weight: 600;
}
/* تأثير الشريط */
.card__ribbon {
position: absolute;
top: 0;
left: 0;
background-color: var(--primary);
color: white;
padding: 0.35rem 2rem;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
transform: rotate(-45deg) translate(-30%, -10%);
transform-origin: center;
z-index: 1;
}
/* تراكب صورة كامل عند التمرير */
.card__image--hover-overlay {
position: relative;
}
.card__image--hover-overlay::before {
content: "عرض التفاصيل";
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.6);
color: white;
font-weight: 600;
font-size: 1rem;
opacity: 0;
transition: opacity 0.3s;
z-index: 1;
}
.card:hover .card__image--hover-overlay::before {
opacity: 1;
}
النافذة المنبثقة / تراكب الحوار
النافذة المنبثقة (تسمى أيضا الحوار) هي نافذة تتراكب فوق محتوى الصفحة الرئيسية وتتطلب تفاعل المستخدم قبل أن يتمكن من العودة إلى الصفحة. تتطلب النوافذ المنبثقة ثلاثة مكونات CSS: خلفية شبه شفافة تغطي منفذ العرض بالكامل، وصندوق محتوى في المنتصف، وانتقالات فتح/إغلاق سلسة. هناك نهجان: تقنية CSS خالصة باستخدام الصنف الزائف :target، وعنصر HTML الحديث <dialog> مع عنصره الزائف ::backdrop.
نافذة منبثقة CSS فقط مع :target
الصنف الزائف :target يطابق عنصرا يتوافق معرفه id مع معرف جزء URL (الجزء بعد #). بالربط إلى id النافذة المنبثقة، يمكنك إظهارها وإخفاؤها بدون JavaScript. ينقر المستخدم على رابط بـ href="#modal-id" لفتحها، ورابط إغلاق بـ href="#" أو href="#!" لإغلاقها.
نافذة منبثقة CSS فقط باستخدام :target
<!-- رابط التشغيل -->
<a href="#demo-modal">فتح النافذة المنبثقة</a>
<!-- بنية النافذة المنبثقة -->
<div class="modal" id="demo-modal">
<div class="modal__backdrop"></div>
<div class="modal__content" role="dialog" aria-labelledby="modal-title">
<h2 id="modal-title">تأكيد الإجراء</h2>
<p>هل أنت متأكد أنك تريد المتابعة؟</p>
<div class="modal__actions">
<a href="#!" class="modal__close">إلغاء</a>
<a href="#!" class="modal__confirm">تأكيد</a>
</div>
</div>
</div>
/* CSS */
.modal {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s, visibility 0.3s;
}
/* إظهار عند الاستهداف */
.modal:target {
opacity: 1;
visibility: visible;
}
.modal__backdrop {
position: absolute;
inset: 0;
background-color: rgba(0, 0, 0, 0.5);
}
.modal__content {
position: relative;
background: var(--bg-white);
border-radius: 8px;
padding: 2rem;
max-width: 480px;
width: 90%;
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2);
transform: translateY(-20px);
transition: transform 0.3s;
}
.modal:target .modal__content {
transform: translateY(0);
}
.modal__actions {
display: flex;
gap: 0.75rem;
justify-content: flex-end;
margin-top: 1.5rem;
}
.modal__close,
.modal__confirm {
padding: 0.5rem 1.25rem;
border-radius: 4px;
text-decoration: none;
font-weight: 600;
font-size: 0.9rem;
}
.modal__close {
color: var(--text-dark);
background-color: var(--bg-light);
}
.modal__confirm {
color: white;
background-color: var(--primary);
}
:target لها قيود. تغير هاش URL مما يؤثر على سجل المتصفح -- الضغط على زر الرجوع يغلق النافذة المنبثقة بدلا من التنقل إلى الصفحة السابقة. كما أنها لا تستطيع حصر تركيز لوحة المفاتيح داخل النافذة المنبثقة، وهو متطلب إمكانية وصول لحوارات النوافذ المنبثقة الحقيقية. لتطبيقات الإنتاج، يوصى بشدة باستخدام عنصر <dialog> الأصلي لأنه يتعامل مع حصر التركيز وإغلاق مفتاح Escape والتفاعلات مع الخلفية بشكل أصلي.عنصر dialog الأصلي و ::backdrop
عنصر HTML <dialog> هو الطريقة الحديثة وسهلة الوصول لإنشاء النوافذ المنبثقة. عند فتحه بطريقة showModal() (عبر استدعاء JavaScript صغير)، ينشئ تلقائيا خلفية ويحصر تركيز لوحة المفاتيح داخل الحوار ويغلق بمفتاح Escape ويعيد التركيز إلى العنصر المشغل عند الإغلاق. CSS يتحكم في المظهر المرئي بما في ذلك العنصر الزائف ::backdrop.
تنسيق عنصر dialog الأصلي
<!-- HTML -->
<button onclick="document.getElementById('my-dialog').showModal()">
فتح الحوار
</button>
<dialog id="my-dialog">
<h2>عنوان الحوار</h2>
<p>هذا حوار أصلي مع ميزات إمكانية وصول مدمجة.</p>
<form method="dialog">
<button value="cancel">إلغاء</button>
<button value="confirm">تأكيد</button>
</form>
</dialog>
/* CSS */
dialog {
border: none;
border-radius: 12px;
padding: 2rem;
max-width: 500px;
width: 90%;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
}
/* تنسيق الخلفية */
dialog::backdrop {
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
}
/* حركة الفتح */
dialog[open] {
animation: dialogFadeIn 0.3s ease forwards;
}
@keyframes dialogFadeIn {
from {
opacity: 0;
transform: translateY(-20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
dialog h2 {
margin: 0 0 0.75rem;
font-size: 1.25rem;
color: var(--text-dark);
}
dialog p {
color: var(--text-light);
line-height: 1.6;
margin-bottom: 1.5rem;
}
dialog form {
display: flex;
gap: 0.75rem;
justify-content: flex-end;
}
dialog button {
padding: 0.5rem 1.25rem;
border: none;
border-radius: 4px;
font-weight: 600;
font-size: 0.9rem;
cursor: pointer;
}
dialog button[value="cancel"] {
background-color: var(--bg-light);
color: var(--text-dark);
}
dialog button[value="confirm"] {
background-color: var(--primary);
color: white;
}
<form method="dialog"> داخل <dialog> هو نموذج خاص يغلق الحوار عند الإرسال بدون إجراء طلب HTTP. سمة value على زر الإرسال تمرر إلى خاصية returnValue للحوار، مما يتيح لك تحديد أي زر نقره المستخدم. هذه هي أنظف طريقة للتعامل مع أزرار الحوار.تنسيق التوست / الإشعارات
التوست هي إشعارات غير معرقلة تظهر مؤقتا على حافة الشاشة -- عادة أعلى اليمين أو أسفل الوسط. تبلغ المستخدم عن الإجراءات المكتملة أو الأخطاء أو التحديثات بدون مقاطعة سير عملهم. ينزلق التوست أو يتلاشى للداخل، يعرض لبضع ثوانٍ، ثم يختفي. CSS يتعامل مع تحديد الموقع والمظهر وحركات الدخول/الخروج.
مكون إشعار التوست
/* حاوية التوست: تحمل جميع التوست في مكدس */
.toast-container {
position: fixed;
top: 1.5rem;
right: 1.5rem;
z-index: 2000;
display: flex;
flex-direction: column;
gap: 0.75rem;
max-width: 380px;
width: 100%;
pointer-events: none;
}
.toast {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 1rem 1.25rem;
background: var(--bg-white);
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
border-left: 4px solid var(--text-light);
pointer-events: auto;
animation: toastSlideIn 0.3s ease forwards;
}
/* المتغيرات */
.toast--success {
border-left-color: #22c55e;
}
.toast--error {
border-left-color: #ef4444;
}
.toast--warning {
border-left-color: #f59e0b;
}
.toast--info {
border-left-color: var(--primary);
}
.toast__icon {
flex-shrink: 0;
width: 1.25rem;
height: 1.25rem;
margin-top: 0.1rem;
}
.toast__content {
flex: 1;
}
.toast__title {
font-weight: 600;
font-size: 0.9rem;
color: var(--text-dark);
margin-bottom: 0.25rem;
}
.toast__message {
font-size: 0.85rem;
color: var(--text-light);
line-height: 1.4;
}
@keyframes toastSlideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
التلميح بـ CSS فقط
توفر التلميحات معلومات إضافية عندما يمرر المستخدم فوق عنصر أو يركز عليه. تلميح CSS الخالص يستخدم سمة data-tooltip مخصصة لمحتوى النص والعنصر الزائف ::after لعرضه. العنصر الزائف ::before ينشئ سهما مثلثا صغيرا يشير إلى العنصر المشغل.
تلميح CSS خالص
<!-- HTML: أضف data-tooltip لأي عنصر -->
<button class="tooltip" data-tooltip="حفظ تقدمك الحالي">
حفظ
</button>
/* CSS */
.tooltip {
position: relative;
cursor: pointer;
}
/* فقاعة التلميح */
.tooltip::after {
content: attr(data-tooltip);
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
padding: 0.5rem 0.85rem;
background-color: var(--text-dark);
color: white;
font-size: 0.8rem;
font-weight: 500;
line-height: 1.4;
white-space: nowrap;
border-radius: 4px;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s, visibility 0.2s;
pointer-events: none;
z-index: 100;
}
/* السهم */
.tooltip::before {
content: "";
position: absolute;
bottom: calc(100% + 2px);
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: var(--text-dark);
opacity: 0;
visibility: hidden;
transition: opacity 0.2s, visibility 0.2s;
pointer-events: none;
z-index: 100;
}
/* إظهار عند التمرير والتركيز */
.tooltip:hover::after,
.tooltip:hover::before,
.tooltip:focus-visible::after,
.tooltip:focus-visible::before {
opacity: 1;
visibility: visible;
}
/* متغير التلميح السفلي */
.tooltip--bottom::after {
bottom: auto;
top: calc(100% + 8px);
}
.tooltip--bottom::before {
bottom: auto;
top: calc(100% + 2px);
border-top-color: transparent;
border-bottom-color: var(--text-dark);
}
::after لا تعلنه جميع قارئات الشاشة بشكل موثوق، وأجهزة اللمس ليس لها حالة تمرير. لتلميحات الإنتاج، فكر في إضافة سمات role="tooltip" وaria-describedby مع عناصر DOM حقيقية لضمان دعم قارئات الشاشة. نهج CSS الخالص الموضح هنا مناسب للتحسين التدريجي حيث يوفر التلميح معلومات تكميلية غير أساسية.الأكورديون مع details و summary
عناصر HTML <details> و<summary> تنشئ أداة كشف أصلية تتوسع وتنطوي بدون أي JavaScript. يتعامل المتصفح مع سلوك التبديل؛ و CSS يتعامل مع العرض المرئي. هذه هي الطريقة الأكثر سهولة في الوصول لبناء مكونات الأكورديون لأن تفاعل لوحة المفاتيح ودلالات ARIA وحالة التبديل كلها تدار بشكل أصلي.
أكورديون منسق مع details/summary
<!-- HTML -->
<div class="accordion">
<details class="accordion__item">
<summary class="accordion__trigger">
ما هو CSS؟
</summary>
<div class="accordion__content">
<p>CSS (أوراق الأنماط المتتالية) هي لغة أوراق أنماط تستخدم لوصف عرض مستند مكتوب بـ HTML أو XML.</p>
</div>
</details>
<details class="accordion__item">
<summary class="accordion__trigger">
كيف تعمل الخصوصية؟
</summary>
<div class="accordion__content">
<p>خصوصية CSS تحدد أي الأنماط تطبق عندما تستهدف قواعد متعددة نفس العنصر. الأنماط المضمنة لها أعلى خصوصية، تليها المعرفات ثم الأصناف ثم العناصر.</p>
</div>
</details>
</div>
/* CSS */
.accordion {
max-width: 640px;
border: 1px solid var(--border-light);
border-radius: 8px;
overflow: hidden;
}
.accordion__item {
border-bottom: 1px solid var(--border-light);
}
.accordion__item:last-child {
border-bottom: none;
}
.accordion__trigger {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem;
font-weight: 600;
font-size: 1rem;
color: var(--text-dark);
cursor: pointer;
list-style: none;
transition: background-color 0.2s;
}
/* إزالة مثلث العلامة الافتراضي */
.accordion__trigger::-webkit-details-marker {
display: none;
}
.accordion__trigger::marker {
display: none;
content: "";
}
/* أيقونة سهم مخصصة */
.accordion__trigger::after {
content: "+";
font-size: 1.25rem;
font-weight: 400;
color: var(--text-light);
transition: transform 0.3s;
}
.accordion__item[open] .accordion__trigger::after {
content: "−";
}
.accordion__trigger:hover {
background-color: var(--bg-light);
}
.accordion__content {
padding: 0 1.25rem 1.25rem;
color: var(--text-light);
line-height: 1.7;
}
name على عناصر <details>. عندما تشترك عناصر <details> المتعددة في نفس قيمة name، يغلق المتصفح تلقائيا العناصر الأخرى عند فتح واحد. هذه الميزة مدعومة في المتصفحات الحديثة ولا تتطلب JavaScript: <details name="faq">.أشرطة التقدم
أشرطة التقدم تنقل حالة الإكمال. بينما يوجد عنصر <progress> الأصلي، فإن تنسيقه عبر المتصفحات غير متسق. شريط تقدم CSS مخصص مبني بعنصري <div> متداخلين يوفر تحكما بصريا كاملا. العنصر الخارجي هو المسار والعنصر الداخلي هو شريط الملء، بحجم ديناميكي بنسبة عرض أو خاصية CSS مخصصة.
شريط تقدم مخصص
<!-- HTML -->
<div class="progress" role="progressbar" aria-valuenow="65" aria-valuemin="0" aria-valuemax="100" aria-label="إكمال الملف الشخصي">
<div class="progress__fill" style="--progress: 65%">
<span class="progress__label">65%</span>
</div>
</div>
/* CSS */
.progress {
width: 100%;
height: 1.25rem;
background-color: var(--bg-light);
border-radius: 999px;
overflow: hidden;
}
.progress__fill {
height: 100%;
width: var(--progress, 0%);
background: linear-gradient(90deg, var(--primary), var(--primary-light));
border-radius: 999px;
display: flex;
align-items: center;
justify-content: flex-end;
transition: width 0.5s ease;
}
.progress__label {
padding-right: 0.5rem;
font-size: 0.7rem;
font-weight: 700;
color: white;
}
/* متغير رفيع */
.progress--thin {
height: 6px;
}
.progress--thin .progress__label {
display: none;
}
/* متغيرات الألوان */
.progress--success .progress__fill {
background: linear-gradient(90deg, #22c55e, #4ade80);
}
.progress--warning .progress__fill {
background: linear-gradient(90deg, #f59e0b, #fbbf24);
}
.progress--danger .progress__fill {
background: linear-gradient(90deg, #ef4444, #f87171);
}
/* تقدم مخطط متحرك */
.progress--striped .progress__fill {
background-image: linear-gradient(
45deg,
rgba(255, 255, 255, 0.2) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.2) 50%,
rgba(255, 255, 255, 0.2) 75%,
transparent 75%
);
background-size: 1rem 1rem;
animation: progressStripes 1s linear infinite;
}
@keyframes progressStripes {
from { background-position: 1rem 0; }
to { background-position: 0 0; }
}
role="progressbar" وaria-valuenow وaria-valuemin وaria-valuemax وaria-label تضمن أن قارئات الشاشة يمكنها الإعلان عن حالة التقدم. بدون هذه السمات، يكون شريط التقدم غير مرئي لمستخدمي تقنيات المساعدة.مكونات الصورة الرمزية
الصور الرمزية هي صور دائرية أو مستديرة صغيرة تمثل المستخدمين. تظهر في التعليقات وقوائم المستخدمين ورؤوس التنقل وأقسام الفريق. نمط CSS يستخدم border-radius: 50% للأشكال الدائرية وobject-fit: cover لمنع تشوه الصورة، واختياريا يستخدم الأحرف الأولى كبديل عندما لا تتوفر صورة.
متغيرات مكون الصورة الرمزية
/* صورة رمزية دائرية أساسية */
.avatar {
width: 3rem;
height: 3rem;
border-radius: 50%;
object-fit: cover;
border: 2px solid var(--border-light);
}
/* متغيرات الحجم */
.avatar--sm { width: 2rem; height: 2rem; }
.avatar--md { width: 3rem; height: 3rem; }
.avatar--lg { width: 4.5rem; height: 4.5rem; }
.avatar--xl { width: 6rem; height: 6rem; }
/* بديل الأحرف الأولى (بدون صورة) */
.avatar-initials {
width: 3rem;
height: 3rem;
border-radius: 50%;
background-color: var(--primary);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 1rem;
text-transform: uppercase;
}
/* صورة رمزية مع مؤشر حالة */
.avatar-wrapper {
position: relative;
display: inline-block;
}
.avatar-status {
position: absolute;
bottom: 2px;
right: 2px;
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid var(--bg-white);
}
.avatar-status--online { background-color: #22c55e; }
.avatar-status--offline { background-color: #94a3b8; }
.avatar-status--busy { background-color: #ef4444; }
/* مجموعة صور رمزية: صور متداخلة */
.avatar-group {
display: flex;
}
.avatar-group .avatar {
margin-left: -0.75rem;
border: 2px solid var(--bg-white);
transition: transform 0.2s;
}
.avatar-group .avatar:first-child {
margin-left: 0;
}
.avatar-group .avatar:hover {
transform: translateY(-4px);
z-index: 1;
}
/* عدد تجاوز مجموعة الصور الرمزية */
.avatar-group__overflow {
width: 3rem;
height: 3rem;
border-radius: 50%;
background-color: var(--bg-light);
color: var(--text-light);
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
font-weight: 600;
margin-left: -0.75rem;
border: 2px solid var(--bg-white);
}
شاشات التحميل الهيكلية
الشاشات الهيكلية هي عناصر واجهة مستخدم مؤقتة تحاكي شكل المحتوى الحقيقي أثناء تحميل البيانات. تحسن الأداء المدرك بإعطاء المستخدمين بنية بصرية فورية بدلا من شاشة فارغة أو مؤشر دوران. تستخدم التقنية أشكالا رمادية مؤقتة مع حركة وميض تجتاح السطح، مشيرة إلى أن المحتوى يتم تحميله.
مكون التحميل الهيكلي
<!-- بطاقة هيكلية مؤقتة -->
<div class="skeleton-card" aria-hidden="true">
<div class="skeleton skeleton--image"></div>
<div class="skeleton-card__body">
<div class="skeleton skeleton--title"></div>
<div class="skeleton skeleton--text"></div>
<div class="skeleton skeleton--text skeleton--short"></div>
</div>
</div>
/* CSS */
.skeleton {
background-color: #e2e8f0;
border-radius: 4px;
position: relative;
overflow: hidden;
}
/* حركة الوميض */
.skeleton::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.5),
transparent
);
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
/* متغيرات الشكل */
.skeleton--image {
width: 100%;
height: 200px;
border-radius: 8px 8px 0 0;
}
.skeleton--title {
width: 70%;
height: 1.25rem;
margin-bottom: 0.75rem;
}
.skeleton--text {
width: 100%;
height: 0.85rem;
margin-bottom: 0.5rem;
}
.skeleton--short {
width: 40%;
}
.skeleton--circle {
width: 3rem;
height: 3rem;
border-radius: 50%;
}
/* تخطيط البطاقة الهيكلية */
.skeleton-card {
background: var(--bg-white);
border: 1px solid var(--border-light);
border-radius: 8px;
overflow: hidden;
}
.skeleton-card__body {
padding: 1.25rem;
}
/* هيكل لعناصر القائمة */
.skeleton-list-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
border-bottom: 1px solid var(--border-light);
}
.skeleton-list-item .skeleton--circle {
flex-shrink: 0;
}
.skeleton-list-item__text {
flex: 1;
}
/* شبكة بطاقات هيكلية */
.skeleton-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
}
aria-hidden="true" إلى العناصر المؤقتة الهيكلية حتى تتخطاها قارئات الشاشة تماما. عندما يتم تحميل المحتوى الحقيقي ويحل محل الهيكل، ستعلن قارئة الشاشة بشكل طبيعي عن المحتوى الفعلي. بالإضافة إلى ذلك، فكر في إضافة <p> مخفي بصريا مع role="status" ونص مثل "جارٍ تحميل المحتوى..." بالقرب من المنطقة الهيكلية حتى يعرف مستخدمو قارئات الشاشة أن شيئا ما يتم تحميله.تجميع كل شيء معا: لوحة تحكم واجهة مستخدم عملية
تجمع واجهات العالم الحقيقي بين أنماط واجهة مستخدم متعددة في صفحة واحدة. قد تتضمن لوحة التحكم شبكة بطاقات لمقاييس النظرة العامة، ومجموعة صور رمزية لأعضاء الفريق، وأشرطة تقدم لحالة المشروع، وتوست للإشعارات، ونافذة منبثقة لتأكيد الإجراءات. فهم كيف تتركب هذه المكونات معا بنفس أهمية معرفة كيفية بناء كل واحد على حدة. اجعل مكوناتك نمطية: كل نمط يجب أن يكون صنف CSS مستقل يعمل بغض النظر عن مكان وضعه في DOM.
تركيب المكونات: بطاقة إحصائيات مع شريط تقدم
<!-- بطاقة تحتوي على إحصائية وشريط تقدم -->
<article class="card stat-card">
<div class="stat-card__header">
<span class="stat-card__label">التخزين المستخدم</span>
<span class="stat-card__value">7.2 جيجابايت</span>
</div>
<div class="progress" role="progressbar" aria-valuenow="72" aria-valuemin="0" aria-valuemax="100">
<div class="progress__fill" style="--progress: 72%"></div>
</div>
<p class="stat-card__detail">2.8 جيجابايت متاحة من 10 جيجابايت</p>
</article>
/* CSS */
.stat-card {
padding: 1.5rem;
}
.stat-card__header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 1rem;
}
.stat-card__label {
font-size: 0.85rem;
color: var(--text-light);
font-weight: 500;
}
.stat-card__value {
font-size: 1.5rem;
font-weight: 700;
color: var(--text-dark);
}
.stat-card .progress {
height: 8px;
margin-bottom: 0.75rem;
}
.stat-card__detail {
font-size: 0.8rem;
color: var(--text-light);
margin: 0;
}
تمرين عملي
ابنِ صفحة مكتبة مكونات واجهة مستخدم كاملة تعرض جميع الأنماط المغطاة في هذا الدرس. ابدأ بإنشاء شبكة بطاقات متجاوبة بأربع بطاقات على الأقل، كل منها يحتوي على صورة وعنوان ووصف وأزرار إجراء. أضف تأثير رفع عند التمرير مع ظل وانتقال تكبير صورة لكل بطاقة. اجعل بطاقة واحدة متغيرا أفقيا مع الصورة على اليسار. أضف شارة بنص "جديد" لبطاقة واحدة على الأقل وتراكب تدرج صورة مع نص على أخرى. بعد ذلك، ابنِ نافذة منبثقة تفتح عندما ينقر المستخدم على أحد أزرار إجراء البطاقة. استخدم عنصر <dialog> الأصلي مع ::backdrop منسق وحركة فتح. أسفل البطاقات، أنشئ قسم أسئلة شائعة باستخدام عناصر <details> و<summary> منسقة كأكورديون بأربعة عناصر على الأقل تشترك في نفس سمة name لسلوك الفتح الحصري. أضف صفا من ثلاثة أشرطة تقدم بمستويات ملء مختلفة ومتغيرات ألوان (نجاح، تحذير، خطر). أضف مجموعة صور رمزية بخمس صور دائرية متداخلة وشارة عدد تجاوز. أخيرا، أنشئ حالة تحميل هيكلية من ثلاث بطاقات تطابق أبعاد بطاقاتك الحقيقية مكتملة مع حركة الوميض. تأكد من أن جميع المكونات التفاعلية لها مؤشرات تركيز مرئية، وأن جميع الصور لها نص بديل، وأن أشرطة التقدم لها سمات ARIA مناسبة، وأن العناصر المؤقتة الهيكلية محددة بـ aria-hidden="true".