# Duyarlı ve Erişilebilir Markup

https://dev.wisecp.com/tr/duyarli-ve-erisilebilir-markup

Bir temanın markup'ının karşılaması gereken ölçülebilir sözleşme ve her kuralın nasıl kanıtlandığı.

## Genel Bakış

Duyarlılık ve erişilebilirlik sonda yapılan bir gözden geçirme değildir. Kabukta birkaç bildirim ve canlı sayfada ölçülen birkaç kuraldır.

İki yükümlülük yalnız temaya aittir: her layout'taki viewport bildirimi ve kökteki dil ile yön öznitelikleri.

## Ön Koşullar

- En az bir layout'u olan bir tema; layout, kendi belge başlığını açan her şablondur.
- Bootstrap 5 anlambilimi. Sürüm 4 sınıf adı hata değildir: sessizce stilsiz kalır.
- İçinde ölçüm yapabileceğiniz bir tarayıcı.

## Yapı

### Layout Bildirimleri

Layout başına üç bildirim; motor hiçbirini eklemez.

1. Viewport meta'sı, tam olarak `width=device-width, initial-scale=1`. Onsuz telefon masaüstü genişliğinde yerleştirir.
2. Kök elemanda dil ve yazım yönü, motorun değişkenlerinden.
3. İlk boyamadan önce yön farkındalıklı stil dosyası seçimi, head guard'ının içinde.

Tema başına denetim: `grep -L 'name="viewport"' templates/website/<Tema>/layouts/*.tpl` hiçbir şey göstermemeli.

### Motor Değişkenleri

| Değişken | Biçimi | Nereden çözülür |
| --- | --- | --- |
| `$ui_lang` | `en` ya da `tr` gibi bir dil kodu. | Seçili dil; her istekte yeniden çözülür. |
| `$ui_dir` | Tam olarak `ltr` ya da `rtl`, başka hiçbir şey. | Dil paketinin kendi yön işareti. |
| `$setting` | Tema manifestindeki her alan, kayıtlı değerlerle birleşmiş. | Temanın ayar şeması; yalnız operatöre ait yerleşim anahtarları için. |

## Referans

### Tema API'si

```php
// coremio/classes/Theme.php
// {asset path='...'} bunu çağırır. YALNIZ css ve js'e dosyanın değişiklik zamanıyla ?v= ekler.
// Font ve görseller bilerek sorgusuz kalır: sürümlenmiş bir font preload'u, font stil dosyasının
// içindeki sorgusuz url() ile hiçbir zaman eşleşmez, preload boşa gider ve yüz ilk boyamayı kaçırır.
public function assetUrl(string $path = ''): string;

// {lang key='...' var='...'} bunu çağırır. 'key' ve 'g' dışındaki her adlandırılmış argüman
// değerde {var} yerine geçer; erişilebilir bir adın gerçek sayıyı taşıması böyle olur.
// Yalnız Smarty: Twig fonksiyonu tek başına anahtarı alır, hiçbir değişken iletmez; Twig
// kullanan bir tema yerine koymayı çağrının dışında yapar, okuyucuya ham {count} göndermez.
public function lang(string $key, array $vars = []): string;

// Bu temada views/<view> varsa true; bir kabuğu ona render etmeden önce kapı olarak kullanılır.
public function viewExists(string $view): bool;
```

```php
// coremio/classes/Language.php
public static function selected(): string;
public static function g($key = '', $replaces = [], $slang = ''): array|string|int|bool;
```

```php
// Şablon yalnız sonucu basar; yönü kendisi hiç çözmez.
$ui_lang = Language::selected();
$ui_dir  = Language::g("package/rtl") ? 'rtl' : 'ltr';
```

- **Theme::assetUrl()**: Temanın varlıklarındaki yolu çözer ve sürümler. Elle yazılan bağlantıda cache buster olmaz.
- **Theme::lang()**: Görünür her dize ve görünmez olanların hepsi: aria etiketleri de metindir.
- **Language::selected()**: Kök dil özniteliğine giden kod. Ekran okuyucular telaffuzu ondan alır.

### Kırılımlar

Dördüncü bir sorgu ayrıntı değil karardır.

| Sorgu | Kademe | Buraya ne girer |
| --- | --- | --- |
| `max-width: 575.98px` | Küçük kademenin altı | Telefon sıkıştırması: bir etiketi gizlemek, bir araç çubuğunu toplamak. |
| `max-width: 767.98px` | Orta kademenin altı | Telefondan sonra da yaşayan yerleşim değişiklikleri. |
| `min-width: 992px` | Büyük ve üstü | Yalnız masaüstündeki kabuk öğeleri. Küçük ekran varsayılan olsun diye minimum yazılır. |
| `print` | Yazdırma | Kâğıtta anlamı olmayan kabuk öğeleri. Tek blok. |

### Punto Ölçeği

Var olanın yanına yeni bir boyut asla icat edilmez.

| Sınıf | Değer | 16px kökte | Ne için |
| --- | --- | --- | --- |
| `fs-7` | 0.85rem | 13.6px | İkincil metin: kart gövdeleri, liste satırları, tablo hücreleri. |
| `fs-8` | 0.8rem | 12.8px | Bir kontrolün altındaki ipucu satırları. |
| `fs-9` | 0.7rem | 11.2px | Taban. Yalnız rozetler ve mikro etiketler. |
| Başlıklar | Bootstrap ölçeği | Beşinci başlık basamağı 20px | Bölüm başlıkları. Yardım satırı etiketinden büyük olamaz. |

### Hareket ve Odak

| Tercih | Temanın yaptığı | Nasıl kanıtlanır |
| --- | --- | --- |
| Azaltılmış hareket | Genel bir blok her animasyonu milisaniyenin yüzde birine kısar ve tek yinelemeye zorlar. | Sıfır opaklıkta kalan elemanları sayın; cevap sıfır olmalı. |
| Klavye odağı | Halkalar yalnız klavye gezinmesinde görünür. | Sekmeyle gezin: her durak görünür olmalı. Sonra tıklayın: hiçbir halka kalmamalı. |

### Ekran Dışı Metin

| İhtiyaç | Doğru markup | Yanlışı ne yapar |
| --- | --- | --- |
| Görünür metni olmayan bir kontrol | Dil dosyasından gelen bir aria etiketi. | Sabit yazılmış metin diğer her dilde İngilizce kalır. |
| Yalnız ekran okuyucu için metin | `visually-hidden` | `sr-only` Bootstrap 4 adıdır; Bootstrap 5 tanımlamaz. Onu bir ikon kütüphanesi ayakta tutar. |
| Klavye kullanıcısı için yer imi | Aria etiketli bir nav elemanı ve işaretlenmiş mevcut sayfa. | Adsız gezinme yer imleri, yer imi listesinde ayırt edilemez. |

### Geniş İçerik

| İçerik | Sarmalayıcı | Onsuz |
| --- | --- | --- |
| Telefonun gösterebileceğinden çok kolonu olan tablo | Tablo elemanının etrafında duyarlı tablo sarmalayıcısı. | Sayfanın kendisi yana kayar ve üzerindeki her ekran bunu devralır. |
| Uzun, bölünmemiş bir dize (anahtar, jeton, alan adı) | Kapsayıcıdan genişlik kaldırmak değil, hücrede sarma. | Kapsayıcıyı genişletmek son kolonları kenarın dışına iter. |
| Bir kod bloğu ya da yük | Kendi kaydırma kabı. | Aynı sayfa seviyesinde yana kayma, uzun satır içeren sayfalarda. |

Geniş içerik kendi kutusunun içinde kayar; sayfa gövdesi asla kaymaz.

## Örnek

Yükümlülükler, layout başlığındaki sırayla.

```html
<html lang="{$ui_lang|default:'en'}" dir="{$ui_dir|default:'ltr'}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    {* İlk boyamadan önce: saklı yönü çöz ve eşleşen Bootstrap dosyasını yaz, böylece ilk
       boyanan kare bir kare sonra çevrilmek yerine zaten doğru olsun. *}
    <script>
      var storedDir = localStorage.getItem('acme-dir');
      var rtl = storedDir ? storedDir === 'rtl' : document.documentElement.getAttribute('dir') === 'rtl';
      document.documentElement.setAttribute('dir', rtl ? 'rtl' : 'ltr');
      document.write('<link rel="stylesheet" href="' +
        (rtl ? '{asset path="css/bootstrap.rtl.min.css"}' : '{asset path="css/bootstrap.min.css"}') + '">');
    </script>

    <link rel="stylesheet" href="{asset path='css/default.css'}">
</head>
```

Müşteri panelindeki erişilebilirlik işinin çoğunu taşıyan iki markup kalıbı.

```smarty
<nav class="client-subnav" aria-label="{lang key='website/index/subnav-aria'}">
    <ul>
        <li>
            <a class="client-subnav-link{if $subnav == 'services'} active{/if}"
               {if $subnav == 'services'} aria-current="page"{/if}
               href="{link route='services'}">
                <i class="bi bi-hdd-stack"></i>{lang key='website/index/subnav-services'}
                {if $client_badges.services > 0}
                    {* Sayı tek başına bağlamdan koparsa anlamsızdır, bu yüzden rozet kendini adlandırır. *}
                    <span class="client-subnav-badge"
                          aria-label="{lang key='website/index/subnav-badge-services' count=$client_badges.services}">
                        {$client_badges_text.services}
                    </span>
                {/if}
            </a>
        </li>
    </ul>
</nav>

{* Bootstrap 5 adı. 'sr-only' bir Bootstrap 5 sınıfı değildir; ona güvenmeyin. *}
<span class="visually-hidden">{lang key='website/index/footer-payment-methods'}</span>
```

Bu iki değişken motordan değil, müşteri sayfa verisiyle gelir.

- **$client_badges**: `services`, `domains`, `invoices`, `support` anahtarlı tam sayılar. Bölüm kapalıysa anahtar kaybolmaz, sıfır olur.
- **$client_badges_text**: Aynı anahtarlar, gösterim dizisi olarak. Bunu rozetin içine, ham olanı etiketin içine yazın.

Kabul listesi; her satır canlı sayfada alınan bir ölçümdür.

- **Sayfada yana kayma yok**: 320 CSS piksel genişlikte belgenin kaydırma genişliği pencerenin iç genişliğini aşmamalı.
- **Dokunma hedefleri**: Kısa eksende en az 44 CSS piksel. İkonu değil kutuyu ölçün.
- **Klavyede görünür, işaretçide görünmez odak**: Sekmeyle gezin: her durak görünür. Tıklayın: hiçbir halka sağ çıkmaz.
- **Azaltılmış hareket hiçbir şeyi gizli bırakmaz**: Tercih açıkken sıfır opaklıkta kalan eleman sayısı sıfırdır.
- **Kontrast**: Gövde metninde 4.5'e 1, büyük metinde 3'e 1. Açık ve koyu modda ölçün.
- **Sağdan sola**: Kök yön özniteliğini çevirin ve yeniden gezin. Hiçbir şey üst üste binmez, kırpılmaz.

## Tuzaklar

> **Bootstrap 4 sınıf adı sessizce stilsizdir**
> 
> Kopyalanan markup, işi yapan sınıf eksik olana kadar çalışır. Önce o sınıfı biçimlendiren kuralı bulun.

> **Viewport meta'sı olmayan tek bir layout bütün bir akışı bozar**
> 
> Yükümlülük tema başına değil layout başınadır. Bildirimsiz bir ödeme kabuğu akışı masaüstü genişliğinde açar.

> **Kapsayıcıyı genişletmek gösterdiğinden fazlasını gizler**
> 
> Bir hücrenin genişlik sınırını kaldırmak son kolonları kenardan düşürür. Bunun yerine değerin hücre içinde sarmasına izin verin.

> **Görünmez metin de metindir**
> 
> Aria etiketleri sabit yazılması en kolay dizelerdir, çünkü kimse onları gözden geçirmede görmez.

> **Bir temadaki düzeltme hepsinde düzeltmedir**
> 
> Buradaki her şey görsel kimlik değil davranıştır. Tek temaya uygulanan düzeltme, aynı kusuru başka adla bırakır.

## İlgili Makaleler

- [Tema Anatomisi](https://dev.wisecp.com/tr/tema-anatomisi)
- [Tema Varlıkları](https://dev.wisecp.com/tr/tema-varliklari)
- [Tema Çevirisi](https://dev.wisecp.com/tr/tema-cevirisi)
- [Sayfa Ekranları](https://dev.wisecp.com/tr/sayfa-yuzeyleri)
