Kurs Next.js · Moduł 3: Stylowanie i optymalizacja wizualna

Optymalizacja czcionek i komponent Font w Next.js 16

13 min czytania ·
W tej lekcji14

W Metropolis Quantum, miejscy projektanci wiedzą, że typografia jest kluczowym elementem tożsamości wizualnej miasta. Każdy znak, informacja czy interfejs miejskich terminali wykorzystuje staranne dobrane kroje pisma, które są nie tylko estetyczne, ale również zoptymalizowane pod kątem szybkości wyświetlania i czytelności na różnych urządzeniach. W świecie webowym, czcionki pełnią podobną rolę - są istotnym elementem projektowym, ale mogą też znacząco wpływać na wydajność stron internetowych.

Next.js 16 oferuje zoptymalizowany sposób zarządzania czcionkami poprzez moduł next/font, dostępny od Next.js 13. Ten rozdział poświęcimy poznaniu tego rozwiązania oraz najlepszym praktykom optymalizacji fontów w aplikacjach Next.js.

Dlaczego optymalizacja czcionek jest ważna?

Niestandardowe czcionki internetowe mogą znacząco wpłynąć na wydajność stron:

  1. Wolniejsze ładowanie strony - czcionki internetowe to dodatkowe zasoby, które muszą zostać pobrane
  2. Migotanie tekstu (FOUT - Flash of Unstyled Text) - gdy tekst początkowo wyświetla się domyślną czcionką, a następnie zmienia się na niestandardową po jej załadowaniu
  3. Niewidoczny tekst (FOIT - Flash of Invisible Text) - gdy tekst jest ukryty do momentu załadowania czcionki
  4. Impact na Core Web Vitals - zwłaszcza na Largest Contentful Paint (LCP) i Cumulative Layout Shift (CLS)

System fontów w Next.js 16 rozwiązuje te problemy, zapewniając:

  • Zero dodatkowych żądań HTTP do zewnętrznych serwerów
  • Automatyczne samohostowanie plików czcionek
  • Automatyczny system fallbacków
  • Brak przesunięć układu (layout shift)
  • Wbudowane podpowiedzi podmiany czcionek dla przeglądarek

Komponent Font w Next.js 16

Next.js 16 dostarcza moduł next/font, który obsługuje:

  • Google Fonts (next/font/google)
  • Lokalne pliki czcionek (next/font/local)

Google Fonts

Oto jak używać czcionek Google w Next.js 16:

// app/fonts.ts
import { Inter, Roboto_Mono } from 'next/font/google';

// Zmienne eksportowe pozwalają na ponowne wykorzystanie tych samych instancji fontów
export const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
});

export const roboto_mono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap',
  weight: ['400', '700'],
});

Następnie możesz użyć tych czcionek w swoim layoucie:

// app/layout.tsx
import { inter } from './fonts';
import './globals.css';
import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'Metropolis Quantum',
  description: 'Oficjalny portal informacyjny miasta przyszłości',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="pl" className={inter.className}>
      <body>{children}</body>
    </html>
  );
}

Lokalne pliki czcionek

Możesz również używać lokalnych plików czcionek, co jest zalecane w przypadku własnych, niestandardowych krojów pisma:

// app/fonts.ts
import localFont from 'next/font/local';

export const quantumSans = localFont({
  src: [
    {
      path: '../public/fonts/QuantumSans-Regular.woff2',
      weight: '400',
      style: 'normal',
    },
    {
      path: '../public/fonts/QuantumSans-Bold.woff2',
      weight: '700',
      style: 'normal',
    },
    {
      path: '../public/fonts/QuantumSans-Italic.woff2',
      weight: '400',
      style: 'italic',
    },
  ],
  display: 'swap',
  variable: '--font-quantum-sans',
});

Opcje konfiguracji fontów

Komponent Font w Next.js 16 oferuje szereg opcji konfiguracyjnych:

Dla Google Fonts:

import { Roboto } from 'next/font/google';

const roboto = Roboto({
  weight: ['400', '700'],  // Wybranie konkretnych wag (grubości) czcionki
  style: ['normal', 'italic'],  // Wybranie stylów czcionki
  subsets: ['latin'],  // Wybranie podzbiorów znaków
  display: 'swap',  // Strategia wyświetlania: 'auto', 'block', 'swap', 'fallback', 'optional'
  preload: true,  // Czy preładować czcionkę
  fallback: ['system-ui', 'Arial'],  // Czcionki zapasowe
  adjustFontFallback: true,  // Automatyczne dostosowywanie czcionek zapasowych
  variable: '--font-roboto',  // Zmienna CSS, jeśli używamy API zmiennych
});

Dla lokalnych czcionek:

import localFont from 'next/font/local';

const quantumDisplay = localFont({
  src: [
    {
      path: './fonts/QuantumDisplay-Regular.woff2',
      weight: '400',
      style: 'normal',
    },
    {
      path: './fonts/QuantumDisplay-Bold.woff2',
      weight: '700',
      style: 'normal',
    },
  ],
  display: 'swap',
  preload: true,
  variable: '--font-quantum-display',
  fallback: ['system-ui', 'Arial'],
  adjustFontFallback: true,
});

Używanie fontów w aplikacji

1. Globalne użycie dla całej aplikacji

// app/layout.tsx
import { quantumSans } from './fonts';
import './globals.css';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="pl" className={quantumSans.className}>
      <body>{children}</body>
    </html>
  );
}

2. Używanie zmiennych CSS dla większej elastyczności

// app/fonts.ts
import { Inter, Roboto_Mono } from 'next/font/google';

export const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-inter',
});

export const roboto_mono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap',
  weight: ['400', '700'],
  variable: '--font-roboto-mono',
});
// app/layout.tsx
import { inter, roboto_mono } from './fonts';
import './globals.css';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="pl" className={`${inter.variable} ${roboto_mono.variable}`}>
      <body>{children}</body>
    </html>
  );
}
/* app/globals.css */
:root {
  --font-inter: 'Inter', system-ui, sans-serif;
  --font-roboto-mono: 'Roboto Mono', monospace;
}

body {
  font-family: var(--font-inter);
}

code {
  font-family: var(--font-roboto-mono);
}

3. Używanie dla konkretnych komponentów

// components/Heading.tsx
import { quantumDisplay } from '@/app/fonts';

export function Heading({ children }: { children: React.ReactNode }) {
  return (
    <h1 className={quantumDisplay.className}>
      {children}
    </h1>
  );
}

Zaawansowane techniki optymalizacji czcionek

1. Optymalizacja subsets dla różnych języków

Jeśli Twoja aplikacja obsługuje wiele języków, możesz zoptymalizować pliki fontów, ładując tylko potrzebne podzestawy znaków:

import { Roboto } from 'next/font/google';

// Dla strony po polsku
export const robotoPolish = Roboto({
  subsets: ['latin', 'latin-ext'],  // latin-ext zawiera znaki używane w języku polskim
  weight: ['400', '700'],
});

// Dla strony po japońsku
export const robotoJapanese = Roboto({
  subsets: ['latin', 'japanese'],
  weight: ['400', '700'],
});

2. Używanie czcionek zmiennych (variable fonts)

Czcionki zmienne to nowoczesny format fontów, który pozwala na zapisanie wielu stylów i wag w jednym pliku, co znacznie zmniejsza ilość danych do pobrania:

import { Roboto_Flex } from 'next/font/google';

export const roboto = Roboto_Flex({
  subsets: ['latin'],
  display: 'swap',
  // Variable fonts często oferują szerszy zakres wag
});

3. Preload vs. Lazy loading

Domyślnie Next.js preładuje czcionki, ale możesz wyłączyć tę funkcję dla czcionek, które nie są używane natychmiast:

import { Roboto } from 'next/font/google';

// Główna czcionka, preładowana
export const robotoMain = Roboto({
  subsets: ['latin'],
  weight: ['400', '700'],
  preload: true,
});

// Czcionka używana tylko w niektórych komponentach, bez preładowania
export const robotoSpecial = Roboto({
  subsets: ['latin'],
  weight: ['900'],
  preload: false,
});

4. Strategie wyświetlania (font-display)

Właściwość display określa, jak przeglądarka powinna obsługiwać etap ładowania czcionki:

  • swap (domyślne): Natychmiast pokazuje tekst używając czcionki fallback, zamienia ją na niestandardową czcionkę, gdy ta się załaduje.
  • block: Krótko ukrywa tekst, a następnie pokazuje go z czcionką fallback, zamienia na docelową po załadowaniu.
  • fallback: Podobne do swap, ale z krótkim okresem ukrytego tekstu. Jeśli ładowanie trwa zbyt długo, używana jest czcionka fallback na czas sesji.
  • optional: Podobne do fallback, ale przeglądarka może całkowicie pominąć niestandardową czcionkę, bazując na warunkach sieciowych.
import { Roboto } from 'next/font/google';

// Dla głównego tekstu, używamy swap, aby zapewnić natychmiastową czytelność
export const robotoBody = Roboto({
  subsets: ['latin'],
  weight: ['400'],
  display: 'swap',
});

// Dla ozdobnych nagłówków, możemy użyć optional, gdyż nie są one krytyczne
export const robotoHeading = Roboto({
  subsets: ['latin'],
  weight: ['900'],
  display: 'optional',
});

Implementacja w różnych układach

1. Podstawowy układ z dwiema czcionkami

// app/fonts.ts
import { Inter, Playfair_Display } from 'next/font/google';

export const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-sans',
});

export const playfair = Playfair_Display({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-serif',
});
// app/layout.tsx
import { inter, playfair } from './fonts';
import './globals.css';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="pl" className={`${inter.variable} ${playfair.variable}`}>
      <body>
        <header className={playfair.className}>
          <h1>Metropolis Quantum</h1>
        </header>
        <main className={inter.className}>
          {children}
        </main>
      </body>
    </html>
  );
}
/* app/globals.css */
:root {
  --font-sans: 'Inter', system-ui, sans-serif;
  --font-serif: 'Playfair Display', Georgia, serif;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-serif);
}

body {
  font-family: var(--font-sans);
}

2. Dashboard z czcionką monospacjową dla danych

// app/dashboard/layout.tsx
import { roboto_mono } from '../fonts';

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="dashboard-layout">
      <div className="sidebar">
        {/* Sidebar content */}
      </div>
      <div className={`content ${roboto_mono.className}`}>
        {children}
      </div>
    </div>
  );
}

3. System komponentów typograficznych

// components/Typography.tsx
import { ReactNode } from 'react';
import { inter, playfair, roboto_mono } from '@/app/fonts';

type FontVariant = 'sans' | 'serif' | 'mono';

interface TypographyProps {
  children: ReactNode;
  variant?: FontVariant;
  as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'div';
  className?: string;
}

export function Typography({
  children,
  variant = 'sans',
  as: Component = 'p',
  className = '',
}: TypographyProps) {
  let fontClass = '';

  switch (variant) {
    case 'sans':
      fontClass = inter.className;
      break;
    case 'serif':
      fontClass = playfair.className;
      break;
    case 'mono':
      fontClass = roboto_mono.className;
      break;
  }

  return (
    <Component className={`${fontClass} ${className}`}>
      {children}
    </Component>
  );
}

Przykład użycia:

// app/page.tsx
import { Typography } from '@/components/Typography';

export default function HomePage() {
  return (
    <div>
      <Typography as="h1" variant="serif" className="text-4xl mb-4">
        Witaj w Metropolis Quantum
      </Typography>

      <Typography variant="sans" className="mb-6">
        Miasto przyszłości, w którym technologia spotyka się z codziennością.
      </Typography>

      <div className="code-example">
        <Typography variant="mono" className="text-sm">
          console.log("Hello, Quantum World!");
        </Typography>
      </div>
    </div>
  );
}

Wpływ na Core Web Vitals

Odpowiednia implementacja fontów ma bezpośredni wpływ na kluczowe metryki wydajności:

Largest Contentful Paint (LCP)

Czcionki niestandardowe mogą opóźnić rendering największego elementu treści. Komponent Font w Next.js 16 minimalizuje ten problem przez:

  • Automatyczne samohostowanie fontów
  • Preładowanie krytycznych fontów
  • Strategie wyświetlania umożliwiające natychmiastowe wyświetlanie tekstu

Cumulative Layout Shift (CLS)

Automatyczne obliczanie rozmiarów fontów fallbackowych minimalizuje przesunięcia układu, gdy niestandardowa czcionka jest ostatecznie ładowana.

First Input Delay (FID) i Interaction to Next Paint (INP)

Czcionki nie mają bezpośredniego wpływu na te metryki, ale efektywne ładowanie fontów może zmniejszyć obciążenie wątku głównego przeglądarki.

Analizowanie wydajności czcionek

1. Lighthouse

Używaj narzędzia Lighthouse do analizy wydajności czcionek:

  1. Otwórz narzędzia deweloperskie Chrome (F12)
  2. Przejdź do zakładki "Lighthouse"
  3. Zaznacz "Performance" i uruchom test
  4. Sprawdź sekcję "Opportunities" i "Diagnostics" pod kątem potencjalnych problemów z czcionkami

2. Network panel w DevTools

Analizuj ładowanie czcionek w zakładce Network:

  1. Filtruj po "Font" lub "Woff2"
  2. Sprawdź czas pobierania, rozmiar plików i priorytet ładowania

3. WebPageTest

Narzędzie WebPageTest (webpagetest.org) oferuje szczegółową analizę ładowania zasobów, w tym czcionek, na różnych urządzeniach i łączach internetowych.

Najlepsze praktyki

  1. Używaj komponentu Font z Next.js 16 zamiast importowania czcionek bezpośrednio z Google Fonts lub innych zewnętrznych źródeł
  2. Ogranicz liczbę używanych fontów do 2-3 rodzin (np. jedna dla nagłówków, jedna dla tekstu, ewentualnie jedna monospacjowa)
  3. Wybieraj tylko potrzebne wagi i style - każda dodatkowa waga to dodatkowe dane do pobrania
  4. Używaj subsets specyficznych dla języków Twojej aplikacji
  5. Rozważ czcionki zmienne dla rodzin z wieloma wagami
  6. Dobieraj odpowiednią strategię wyświetlania (display) w zależności od przeznaczenia czcionki
  7. Używaj zmiennych CSS dla większej elastyczności w stylizacji
  8. Testuj wydajność na różnych urządzeniach i połączeniach

Przykład kompleksowej implementacji

Poniżej znajduje się przykład kompleksowej implementacji systemu fontów w aplikacji Next.js 16:

// app/fonts.ts
import { Inter, Playfair_Display, Roboto_Mono } from 'next/font/google';
import localFont from 'next/font/local';

// Główna czcionka dla tekstu
export const inter = Inter({
  subsets: ['latin', 'latin-ext'],
  display: 'swap',
  variable: '--font-sans',
  weight: ['400', '500', '700'],
});

// Czcionka dla nagłówków
export const playfair = Playfair_Display({
  subsets: ['latin', 'latin-ext'],
  display: 'optional',
  variable: '--font-serif',
  weight: ['700', '900'],
});

// Czcionka dla kodu
export const roboto_mono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-mono',
  weight: ['400', '700'],
});

// Własna, niestandardowa czcionka (logo i wyróżnione elementy)
export const quantumDisplay = localFont({
  src: [
    {
      path: '../public/fonts/QuantumDisplay-Regular.woff2',
      weight: '400',
      style: 'normal',
    },
    {
      path: '../public/fonts/QuantumDisplay-Bold.woff2',
      weight: '700',
      style: 'normal',
    },
  ],
  display: 'swap',
  variable: '--font-quantum',
});
// app/layout.tsx
import { inter, playfair, roboto_mono, quantumDisplay } from './fonts';
import './globals.css';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="pl" className={`
      ${inter.variable}
      ${playfair.variable}
      ${roboto_mono.variable}
      ${quantumDisplay.variable}
    `}>
      <body className={inter.className}>{children}</body>
    </html>
  );
}
/* app/globals.css */
:root {
  --font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  --font-serif: 'Playfair Display', Georgia, Cambria, 'Times New Roman', Times, serif;
  --font-mono: 'Roboto Mono', SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
  --font-quantum: 'Quantum Display', var(--font-sans);

  /* Rozmiary fontów */
  --fs-xs: 0.75rem;
  --fs-sm: 0.875rem;
  --fs-base: 1rem;
  --fs-lg: 1.125rem;
  --fs-xl: 1.25rem;
  --fs-2xl: 1.5rem;
  --fs-3xl: 1.875rem;
  --fs-4xl: 2.25rem;
  --fs-5xl: 3rem;

  /* Line-heights */
  --lh-tight: 1.25;
  --lh-snug: 1.375;
  --lh-normal: 1.5;
  --lh-relaxed: 1.625;
  --lh-loose: 2;
}

body {
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-serif);
  line-height: var(--lh-tight);
  font-weight: 700;
}

h1 {
  font-size: var(--fs-4xl);
  margin-bottom: 1.5rem;
}

h2 {
  font-size: var(--fs-3xl);
  margin-bottom: 1.25rem;
}

.logo {
  font-family: var(--font-quantum);
  font-weight: 700;
}

code, pre {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
}

.title-quantum {
  font-family: var(--font-quantum);
  font-weight: 700;
  letter-spacing: -0.025em;
}

Typowe problemy i ich rozwiązania

Problem 1: FOUT (Flash of Unstyled Text)

Rozwiązanie: Używaj display: 'swap' i upewnij się, że czcionki są preładowane.

Problem 2: Duży rozmiar plików czcionek

Rozwiązanie:

  • Ograniczaj liczbę wag i stylów
  • Używaj tylko niezbędnych subsetów
  • Rozważ czcionki zmienne
  • Optymalizuj pliki WOFF2 (Next.js robi to automatycznie)

Problem 3: Czcionki opóźniają ładowanie strony

Rozwiązanie:

  • Dla mniej krytycznych czcionek używaj display: 'optional'
  • Dla czcionek używanych tylko w określonych komponentach wyłącz preładowanie

Problem 4: Różnice w wyglądzie czcionek fallbackowych i niestandardowych

Rozwiązanie: Next.js 16 automatycznie dopasowuje metryki czcionek fallbackowych do docelowych czcionek, minimalizując przesunięcia. Możesz też użyć adjustFontFallback: true.

Kompatybilność z przeglądarkami

Komponent Font w Next.js 16 automatycznie dostarcza czcionki w formacie WOFF2, który jest wspierany przez wszystkie nowoczesne przeglądarki. Dla starszych przeglądarek, które nie obsługują WOFF2, wyświetlane są czcionki fallbackowe zdefiniowane w konfiguracji.

Wsparcie dla języków z niestandardowymi systemami pisma

Przy pracy z aplikacjami wielojęzycznymi, pamiętaj o:

  1. Odpowiednich subsetach dla każdego języka
  2. Większych rozmiarach plików dla języków z rozbudowanymi systemami znaków (np. chiński, japoński)
  3. Wydajności renderingu - niektóre złożone systemy pisma mogą obciążać przeglądarkę
// Przykład dla aplikacji wielojęzycznej
import { Noto_Sans_JP, Noto_Sans } from 'next/font/google';

// Dla języka łacińskiego (np. angielski, polski)
export const notoSansLatin = Noto_Sans({
  subsets: ['latin', 'latin-ext'],
  weight: ['400', '700'],
  variable: '--font-latin',
});

// Dla języka japońskiego
export const notoSansJP = Noto_Sans_JP({
  subsets: ['japanese'],
  weight: ['400', '700'],
  variable: '--font-japanese',
});

Podsumowanie

Komponent Font w Next.js 16 zapewnia kompleksowe rozwiązanie dla zarządzania i optymalizacji czcionek w aplikacjach webowych:

  1. Automatyczne samohostowanie - eliminuje zewnętrzne żądania HTTP
  2. Zoptymalizowany format WOFF2 - mniejsze pliki do pobrania
  3. Zapobieganie layout shift - lepsze CLS
  4. Strategie wyświetlania - kontrola nad sposobem ładowania czcionek
  5. Preload i lazy loading - optymalne wykorzystanie przepustowości sieci

Podobnie jak w Metropolis Quantum, gdzie każdy aspekt wizualnej komunikacji jest starannie zaprojektowany i zoptymalizowany, dobrze zaimplementowany system czcionek w Next.js 16 zapewnia idealną równowagę między estetyką a wydajnością. Dzięki komponentowi Font, aplikacje Next.js mogą oferować spójne, piękne i efektywne doświadczenie typograficzne bez uszczerbku dla wydajności.

W następnym rozdziale omówimy zarządzanie zasobami statycznymi w katalogu public, co pozwoli nam lepiej zrozumieć, jak efektywnie organizować i obsługiwać różne typy zasobów w aplikacjach Next.js 16.

Kod do tej lekcji: app/components/DesignSystem.tsx
// Design System - Quantum UI Library
'use client';

import styles from './DesignSystem.module.css';

// Design Tokens jako const
export const tokens = {
  colors: {
    primary: { main: '#64ffda', light: '#96ffe6', dark: '#32cc99' },
    secondary: { main: '#448aff', light: '#80b3ff', dark: '#0056cc' },
    accent: { main: '#7c4dff', light: '#b47cff', dark: '#4400cc' },
    neutral: { gray900: '#0f0f23', gray700: '#1a1a2e', gray500: '#3d3d55' }
  },
  spacing: { xs: '0.25rem', sm: '0.5rem', md: '1rem', lg: '1.5rem', xl: '2rem' },
  typography: {
    fontFamily: { primary: 'Inter, sans-serif', mono: 'Fira Code, monospace' },
    fontSize: { sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem' },
    fontWeight: { regular: 400, medium: 500, semibold: 600, bold: 700 }
  },
  borderRadius: { sm: '0.25rem', md: '0.5rem', lg: '0.75rem', xl: '1rem' },
  shadows: {
    sm: '0 2px 8px rgba(0,0,0,0.15)',
    md: '0 4px 16px rgba(0,0,0,0.25)',
    lg: '0 8px 32px rgba(0,0,0,0.35)',
    neon: '0 0 20px rgba(100,255,218,0.5)'
  }
};

export default function DesignSystemDemo() {
  return (
    <div className={styles.container}>
      <h1 className={styles.title}>Quantum Design System</h1>

      {/* Color Palette */}
      <section className={styles.section}>
        <h2 className={styles.sectionTitle}>Color Palette</h2>
        <div className={styles.colorGrid}>
          <div className={styles.colorCard}>
            <div className={styles.colorSwatch} style={{background: tokens.colors.primary.main}}></div>
            <p>Primary</p>
            <code>{tokens.colors.primary.main}</code>
          </div>
          <div className={styles.colorCard}>
            <div className={styles.colorSwatch} style={{background: tokens.colors.secondary.main}}></div>
            <p>Secondary</p>
            <code>{tokens.colors.secondary.main}</code>
          </div>
          <div className={styles.colorCard}>
            <div className={styles.colorSwatch} style={{background: tokens.colors.accent.main}}></div>
            <p>Accent</p>
            <code>{tokens.colors.accent.main}</code>
          </div>
        </div>
      </section>

      {/* Typography */}
      <section className={styles.section}>
        <h2 className={styles.sectionTitle}>Typography Scale</h2>
        <div className={styles.typographyDemo}>
          <p style={{fontSize: tokens.typography.fontSize['2xl'], fontWeight: tokens.typography.fontWeight.bold}}>
            Heading 2XL
          </p>
          <p style={{fontSize: tokens.typography.fontSize.xl, fontWeight: tokens.typography.fontWeight.semibold}}>
            Heading XL
          </p>
          <p style={{fontSize: tokens.typography.fontSize.lg, fontWeight: tokens.typography.fontWeight.medium}}>
            Heading LG
          </p>
          <p style={{fontSize: tokens.typography.fontSize.base}}>
            Body Text Base
          </p>
          <p style={{fontSize: tokens.typography.fontSize.sm}}>
            Small Text
          </p>
        </div>
      </section>

      {/* Component Library */}
      <section className={styles.section}>
        <h2 className={styles.sectionTitle}>Component Library</h2>

        {/* Buttons */}
        <div className={styles.componentGroup}>
          <h3>Buttons</h3>
          <div className={styles.buttonShowcase}>
            <button className={styles.btnPrimary}>Primary</button>
            <button className={styles.btnSecondary}>Secondary</button>
            <button className={styles.btnOutline}>Outline</button>
            <button className={styles.btnGhost}>Ghost</button>
          </div>
        </div>

        {/* Cards */}
        <div className={styles.componentGroup}>
          <h3>Cards</h3>
          <div className={styles.cardShowcase}>
            <div className={styles.card}>
              <h4>Standard Card</h4>
              <p>This is a standard card component with consistent padding and styling.</p>
            </div>
            <div className={styles.cardElevated}>
              <h4>Elevated Card</h4>
              <p>This card has elevated shadow for emphasis.</p>
            </div>
            <div className={styles.cardNeon}>
              <h4>Neon Card</h4>
              <p>Cyberpunk-styled card with neon glow effect.</p>
            </div>
          </div>
        </div>

        {/* Form Elements */}
        <div className={styles.componentGroup}>
          <h3>Form Elements</h3>
          <div className={styles.formShowcase}>
            <input type="text" placeholder="Text Input" className={styles.input} />
            <select className={styles.select}>
              <option>Select Option</option>
              <option>Option 1</option>
              <option>Option 2</option>
            </select>
            <textarea placeholder="Textarea" className={styles.textarea}></textarea>
          </div>
        </div>
      </section>

      {/* Spacing System */}
      <section className={styles.section}>
        <h2 className={styles.sectionTitle}>Spacing System</h2>
        <div className={styles.spacingDemo}>
          {Object.entries(tokens.spacing).map(([key, value]) => (
            <div key={key} className={styles.spacingItem}>
              <div className={styles.spacingBar} style={{width: value}}></div>
              <span>{key}: {value}</span>
            </div>
          ))}
        </div>
      </section>

      {/* Shadow System */}
      <section className={styles.section}>
        <h2 className={styles.sectionTitle}>Shadow System</h2>
        <div className={styles.shadowGrid}>
          <div className={styles.shadowBox} style={{boxShadow: tokens.shadows.sm}}>Small</div>
          <div className={styles.shadowBox} style={{boxShadow: tokens.shadows.md}}>Medium</div>
          <div className={styles.shadowBox} style={{boxShadow: tokens.shadows.lg}}>Large</div>
          <div className={styles.shadowBox} style={{boxShadow: tokens.shadows.neon}}>Neon</div>
        </div>
      </section>
    </div>
  );
}

Hasła z dokumentacji w tej lekcji

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. CSS Grid służy do:

  2. 2. Flexbox najlepiej nadaje się do:

Zadania praktyczne w grze

  • Klikanie w kolejności

    Ułóż składnię klasy Tailwind z responsive breakpoint:

  • Edytor kodu

    Utwórz card component używając tylko klas Tailwind CSS (padding, shadow, border-radius, etc.)

  • Układanie w poziomie

    Ułóż składnię importu CSS Module w Next.js:

  • Klikanie w kolejności

    Ułóż składnię importu CSS Module w Next.js

  • Edytor kodu

    Utwórz CSS z techniques optymalizacji: will-change, transform, contain, content-visibility

Przydatne artykuły