Ionic은 프레임워크 통합을 통해 웹 컴포넌트를 제공합니다. 이 샘플은 Ionic Vue 9.0.6을 사용하며 IonicVue 설정에서 mode를 ios로 지정합니다. 따라서 페이지를 확인하는 데스크톱 브라우저와 관계없이 세그먼트, 토글, 범위 컨트롤, 버튼에 일관된 플랫폼 스타일이 적용됩니다. 모드는 컴포넌트 렌더링 방식에 대한 설정이며 페이지를 네이티브 모바일 애플리케이션으로 바꾸지는 않습니다.
이 예제는 의도적으로 전체 화면 IonApp/IonContent 셸 대신 일반 main 요소를 사용합니다. 덕분에 컴포넌트 견본이 임베드된 리소스 페이지 안에서 콘텐츠에 맞게 표시됩니다. 실제 라우팅이 적용된 Ionic 애플리케이션은 전환에 필요한 IonPage를 포함해 프레임워크의 페이지 및 라우터 구조를 따라야 합니다.
설치된 코어 스타일시트에서 기본 색상은 #0054e9로 정의되며, 별도의 RGB 채널, 대비 색상, 명도 변화 색상, 틴트 값이 함께 제공됩니다. 컴포넌트는 상태에 따라 이와 연관된 값을 각각 사용할 수 있습니다. 기본 16진수 색상만 바꾸면 다른 상태의 색상과 일관성이 깨질 수 있으므로, 사용자 지정 브랜드 색상에는 전체 색상군을 정의해야 합니다.
이 견본은 네이티브 컨트롤에 기본 색상을 유지합니다. 따뜻한 종이 색상은 #fff9eb로 지정한 별도의 애플리케이션 표면이며, 문서 텍스트는 #24362e를 유지합니다. 페이지를 흰색으로 바꾸면 읽기 표면만 변경됩니다. 컨트롤 팔레트가 조용히 바뀌거나 Ionic의 전체 다크 팔레트를 구현했다고 주장하지 않습니다.
세그먼트를 사용하면 문서의 글꼴을 Georgia와 system-ui 사이에서 전환할 수 있습니다. 컨트롤 자체에는 계속 system-ui를 사용하므로, 레이블과 컨트롤 크기를 예상치 않게 바꾸지 않고 의도한 읽기 콘텐츠에만 선택 사항을 적용합니다. 외부 글꼴은 요청하지 않으며, 해당하는 로컬 글꼴 스택을 사용합니다.
텍스트 크기는 16~26픽셀의 정수입니다. 문서 제목은 이 크기에 비례하고 줄 높이는 1.65로 유지됩니다. 너비가 360px 미만이면 좁게 임베드된 열에서도 가장 큰 크기를 읽기 쉽도록 제목 비율을 1.6em으로 낮춥니다. 이는 Ionic 컴포넌트 기본값과 명확히 구분되는 애플리케이션 고유 값입니다.
Ionic Framework 디자인 시스템: 선택 상태를 제어하고 네이티브 시맨틱을 갖춘 세그먼트
선택 상태를 제어하고 네이티브 시맨틱을 갖춘 세그먼트
IonSegment는 현재 face 값을 받고 두 개의 IonSegmentButton 자식 요소를 사용합니다. ionChange 이벤트에서 event.detail.value를 가져와 모델을 업데이트합니다. 각 버튼은 serif 또는 sans라는 고유하고 안정적인 값을 가지며, 표시 레이블로 선택 항목을 설명합니다. aria-labelledby를 사용해 전체 컨트롤에 문단 레이블을 지정합니다.
실제 접근성 트리에는 일반 버튼 그룹이 아닌 선택된 탭이 있는 tablist가 노출됩니다. 따라서 로컬 상호작용 테스트에서는 네이티브 Sans 탭을 대상으로 삼고 읽기 글꼴이 바뀌는지 확인합니다. 버튼 두 개를 세그먼트처럼 보이도록 꾸미는 것만으로는 동일한 선택 동작을 구현할 수 없습니다.
Ionic Framework 디자인 시스템: 범위 이벤트와 제약 조건을 하나의 계약으로 다루기
범위 이벤트와 제약 조건을 하나의 계약으로 다루기
IonRange에서 min, max, step을 명시적으로 설정합니다. 기본 0~100 범위에 의존하면 유용한 글자 크기 조절을 표현할 수 없습니다. 스냅과 눈금으로 선택 가능한 단계를 보여 주고, 핀 포맷터는 조작 중 표시되는 값 뒤에 px를 붙입니다. 컨트롤 아래에 표시되는 글자 크기 레이블은 드래그를 멈춘 뒤에도 읽을 수 있습니다.
핸들러는 ionChange를 사용하고 값을 숫자로 변환합니다. 단일 노브에는 적절하지만, 노브가 두 개인 범위에서는 반환되는 값의 형태가 달라 별도의 처리가 필요합니다. 컴파일된 iOS 범위에서는 노브 크기가 26px, 막대 두께가 4px이고, Material 모드에서는 각각 18px와 2px입니다. 이러한 차이는 근거 없이 하나의 Ionic 토큰으로 통일하지 않고 그대로 기록합니다.
텍스트 크기 슬라이더에 초점을 맞춘 다음 오른쪽 화살표를 여덟 번 눌러 26px로 설정합니다.
Ionic Framework 디자인 시스템 종이 배경 옵션을 켜고 끄면 표면에 즉시 반영하기
종이 색상 선택에 따라 표면을 즉시 업데이트하기
IonToggle은 따뜻한 종이 배경 모델의 checked 값을 전달받습니다. 이벤트의 detail.checked 값을 해당 불리언 상태에 바로 할당합니다. 표시되는 ‘따뜻한 종이 배경’ 레이블은 네이티브 스위치에 포함되어 있고, 옆의 설명은 이 옵션이 글의 표면을 바꾼다는 점을 알려 줍니다.
따뜻한 배경 클래스는 테두리를 없애거나 글의 내용을 바꾸지 않고 글 영역의 배경색만 변경합니다. 덕분에 의미 있는 비교가 가능합니다. 또한 종이 색조 환경설정을 플랫폼 수준의 라이트/다크 설정과 혼동하지 않도록 합니다. 플랫폼 설정을 바꾸려면 더 많은 Ionic 표면 및 텍스트 변수를 조정해야 합니다.
각 설정은 글에 즉시 적용됩니다. ‘환경설정 저장’은 읽을 수 있는 스냅샷을 로컬 반응형 상태에 기록하고, 스냅샷이 현재 상태와 일치하는 동안 버튼을 비활성화합니다. 세 모델을 모두 감시하는 watcher는 설정이 하나라도 바뀌면 메시지를 지웁니다. 따라서 이전 확인 메시지가 새 조합을 설명하는 것처럼 보이지 않습니다.
‘기본값 복원’을 누르면 서체는 serif, 크기는 18px, 종이 배경은 따뜻한 색으로 설정되고 저장 메시지는 지워집니다. 상태 메시지는 이 환경설정이 미리보기에만 적용된다는 점을 분명히 알립니다. 이 동작에는 localStorage 저장, 계정 설정 또는 네이티브 환경설정 플러그인이 없으며, 페이지를 새로 불러오면 기본값으로 시작합니다.
Ionic Framework 디자인 시스템 네이티브 Shadow DOM 컨트롤을 유지하면서 구성 스타일 적용하기
네이티브 Shadow DOM 컨트롤을 유지하면서 구성 스타일 적용하기
범위 컨트롤에는 ‘텍스트 크기’라는 레이블이 표시되고, 토글에는 자체 텍스트 레이블이 포함되며, 세그먼트에는 ‘서체’라는 레이블이 연결됩니다. 원본 일러스트에는 aria-hidden이 적용됩니다. 저장 피드백에는 role=status를 사용하고, 글은 일반적인 의미 구조를 갖춘 HTML로 유지하여 제목과 문단을 독자의 일반적인 문서 탐색 흐름에 따르게 합니다.
Ionic 컴포넌트는 shadow DOM 안에 내부 마크업을 캡슐화합니다. 이 예제는 내부 비공개 클래스를 조회하지 않고 공개 속성, 이벤트, 문서화된 CSS 사용자 지정 속성을 사용합니다. 주변 페이지는 콘텐츠 높이를 사용하고 최소 너비를 0으로 설정합니다. 280px 너비에서 확인하면 독립형 휴대전화 스크린샷만으로는 놓칠 수 있는 임베드 너비 문제를 발견할 수 있습니다.
완성된 진입점은 IonicVue를 등록하고 원본 스타일시트보다 먼저 @ionic/vue/css/core.css를 가져옵니다. 설치된 Vue 패키지는 vue-router도 가져오므로, 이 임베드 예제에서 내비게이션을 설정하지 않더라도 재현 가능한 의존성 명령에 vue-router를 포함합니다. 이 피어 의존성을 누락하면 로컬 번들에서 모듈을 찾지 못해 빌드가 실패했습니다.
두 가지 완성 예제에는 App.vue와 style.css가 포함됩니다. 표준 Vite Vue 프로젝트는 SFC와 네이티브 컴포넌트 가져오기를 컴파일합니다. 독립형 리소스 미리보기는 컴파일 결과인 JavaScript와 CSS을 컴포넌트 구현을 포함해 로컬에서 번들링하므로, 컨트롤을 변경해도 외부 CDN 요청이 필요하지 않습니다.
네이티브 IonSegment, IonRange, IonToggle, IonButton으로 새로 작성한 짧은 글을 조작합니다.
vue
<script setup>
import {ref,computed,watch} from 'vue';
import {IonSegment,IonSegmentButton,IonLabel,IonRange,IonToggle,IonButton,IonChip} from '@ionic/vue';
const face=ref('serif'),size=ref(18),warm=ref(true),saved=ref('');
const family=computed(()=>face.value==='serif'?'Georgia, serif':'system-ui, sans-serif');
watch([face,size,warm],()=>saved.value='');
function save(){saved.value=`Saved locally: ${face.value}, ${size.value}px, ${warm.value?'warm':'white'} paper.`}
function reset(){face.value='serif';size.value=18;warm.value=true;saved.value=''}
</script>
<template>
<main class="reader-shell">
<header><p class="eyebrow">A QUIETER READING SPACE</p><h1>Make room for the words.</h1><p>Adjust this short essay to your reading preferences. The sample changes as you move the controls.</p></header>
<div class="reader-layout"><section class="preferences" aria-labelledby="preferences-title"><h2 id="preferences-title">Reading settings</h2><p class="label" id="face-label">Typeface</p>
<IonSegment :value="face" @ionChange="face=$event.detail.value" aria-labelledby="face-label"><IonSegmentButton value="serif"><IonLabel>Serif</IonLabel></IonSegmentButton><IonSegmentButton value="sans"><IonLabel>Sans</IonLabel></IonSegmentButton></IonSegment>
<IonRange :value="size" @ionChange="size=Number($event.detail.value)" :min="16" :max="26" :step="1" :snaps="true" :ticks="true" :pin="true" label="Text size" label-placement="stacked" :pin-formatter="v=>`${v}px`"/><p class="scale"><span>16px</span><strong>{{size}}px</strong><span>26px</span></p>
<div class="paper-toggle"><IonToggle :checked="warm" @ionChange="warm=$event.detail.checked" justify="space-between">Warm paper</IonToggle><p>Soft ivory or a white page. The controls keep their own contrast.</p></div>
<IonButton expand="block" @click="save" :disabled="!!saved">Save preferences</IonButton><IonButton expand="block" fill="clear" @click="reset">Restore defaults</IonButton><p class="save-result" role="status">{{saved||'Preferences apply to this preview only.'}}</p></section>
<article class="reading-paper" :class="{warm}" :style="{fontFamily:family,fontSize:size+'px'}"><div class="paper-art" aria-hidden="true"><span></span><i></i><b></b></div><p class="article-kicker">FIELD NOTES · 03</p><h2>The useful pause</h2><p>There is a small garden between the library and the river. Its benches face away from the busiest path, toward a row of young trees.</p><p>On an ordinary afternoon, the place asks very little. You can read a paragraph twice, let a conversation settle, or watch the shadow of a leaf move across the page.</p><p>Good reading spaces leave that choice with the reader. A comfortable line, a familiar letterform, and a little breathing room can make a page easier to return to.</p><footer><IonChip outline>Original sample essay</IonChip><span>{{size}}px · {{face==='serif'?'Georgia':'System sans'}} · 1.65 line height</span></footer></article></div>
</main>
</template>
Ionic Vue/Core 9.0.6을 확인했습니다. 완성된 SFC와 스타일시트는 위에 있으며, 미리보기에는 라우팅된 페이지가 없지만 통합 의존성으로 vue-router를 설치합니다.
자바스크립트
// Start with: npm create vite@latest ionic-reader -- --template vue
// cd ionic-reader && npm install
// npm install @ionic/[email protected] @ionic/[email protected] vue-router@4
// Save the complete examples as src/App.vue and src/style.css.
// src/main.js:
import {createApp} from 'vue';
import {IonicVue} from '@ionic/vue';
import '@ionic/vue/css/core.css';
import './style.css';
import App from './App.vue';
createApp(App).use(IonicVue, {mode:'ios'}).mount('#app');
// The Vite index.html must contain:
// <div id="app"></div><script type="module" src="/src/main.js"></script>
// Run: npm run dev
프로젝트를 위한 프롬프트
에이전트 프롬프트
제공된 읽기 환경설정 예제를 Ionic Vue/Core 9.0.6의 명시적인 iOS 모드에서 구현합니다. 글꼴 선택에는 네이티브 Segment, 텍스트 크기에는 Range, 종이 배경에는 Toggle, 작업에는 Button을 사용하고 실제 글 스타일과 연결합니다. 크기 범위 16~26px를 유지하고 변경 사항을 로컬 저장 환경설정 스냅샷에 반영합니다. IonicVue 설정, 필수 CSS 및 vue-router 피어 의존성을 포함합니다. 임베드 안에 전체 화면 셸을 추가하지 말고 콘텐츠 높이에 맞춘 구성을 유지합니다. 검사 결과는 업로드되지 않으며 원격 계정도 변경하지 않습니다.
05
Ionic Framework 디자인 시스템 출처 및 범위
Ionic Vue/Core 9.0.6, iOS 모드. 글, 일러스트, 읽기 크기, 따뜻한 종이 배경 색상은 직접 제작했습니다. 저장은 로컬 반응형 상태로 처리되며 페이지를 새로 불러와도 유지되지 않습니다. 이 임베드 컴포넌트 예제는 라우팅을 포함한 완전한 앱이나 네이티브 기기용 Ionic 앱이 아닙니다.