Flutter 튜토리얼 23편: 커스텀 폰트와 타이포그래피

요약#

핵심 요지#

  • 문제 정의: 기본 시스템 폰트만으로는 브랜드 아이덴티티를 표현하기 어렵고, 텍스트 스타일을 일관되게 관리하기 번거롭다.
  • 핵심 주장: pubspec.yaml에 폰트를 등록하거나 google_fonts1 패키지를 사용하면 커스텀 폰트를 쉽게 적용할 수 있다.
  • 주요 근거: TextTheme2의 15가지 텍스트 스타일(displayLarge~bodySmall)을 정의하면 앱 전체에서 일관된 타이포그래피를 유지할 수 있다.
  • 실무 기준: google_fonts 패키지를 사용하면 1,000개 이상의 오픈소스 폰트를 별도 파일 없이 사용할 수 있다.
  • 한계: 가변 폰트(Variable Font)의 모든 축(axis)을 지원하지 않을 수 있다.

문서가 설명하는 범위#

  • 로컬 폰트 파일 등록과 사용법
  • google_fonts 패키지 활용
  • TextTheme으로 타이포그래피 시스템 구축
  • 폰트 웨이트와 스타일 설정

읽는 시간: 12분 | 난이도: 초급


참고 자료#


문제 상황#

앱에서 텍스트 스타일을 관리할 때 여러 어려움이 있습니다.

폰트와 타이포그래피 관리의 어려움#

문제 1: 기본 폰트로는 브랜드 아이덴티티 표현이 어려움
문제 2: 텍스트 크기, 굵기를 각 위젯에서 개별 지정 → 일관성 유지 어려움
문제 3: 폰트 파일 관리 → 웨이트별로 여러 파일 필요
문제 4: 다국어 지원 → 언어별로 다른 폰트가 필요할 수 있음

해결 방법#

Flutter는 커스텀 폰트와 체계적인 타이포그래피 시스템을 지원합니다. 로컬 파일이나 Google Fonts를 통해 폰트를 적용하고, TextTheme으로 일관된 스타일을 관리합니다.

챕터 1: 로컬 폰트 파일 등록하기#

Why#

NOTE

브랜드 전용 폰트나 라이선스가 있는 상용 폰트를 사용하려면 폰트 파일을 직접 프로젝트에 포함해야 합니다.
pubspec.yaml에 폰트를 등록하면 앱 번들에 포함됩니다.

폰트 파일 준비 → pubspec.yaml 등록 → TextStyle에서 fontFamily 지정

What#

NOTE

Flutter는 TrueType(.ttf), OpenType(.otf), OpenType Collection(.ttc) 폰트를 지원합니다.
웹 전용 포맷(.woff, .woff2)은 데스크톱 플랫폼에서 지원하지 않습니다.

How#

TIP

프로젝트 구조

my_app/
├── fonts/
│ ├── Pretendard-Regular.ttf
│ ├── Pretendard-Medium.ttf
│ ├── Pretendard-SemiBold.ttf
│ ├── Pretendard-Bold.ttf
│ └── Pretendard-ExtraBold.ttf
├── lib/
│ └── main.dart
└── pubspec.yaml

pubspec.yaml 설정

flutter:
fonts:
- family: Pretendard
fonts:
- asset: fonts/Pretendard-Regular.ttf
weight: 400
- asset: fonts/Pretendard-Medium.ttf
weight: 500
- asset: fonts/Pretendard-SemiBold.ttf
weight: 600
- asset: fonts/Pretendard-Bold.ttf
weight: 700
- asset: fonts/Pretendard-ExtraBold.ttf
weight: 800

앱 전체 기본 폰트 설정

MaterialApp(
theme: ThemeData(
fontFamily: 'Pretendard',
),
)

특정 위젯에서 사용

Text(
'커스텀 폰트 텍스트',
style: TextStyle(
fontFamily: 'Pretendard',
fontWeight: FontWeight.w600,
),
)

Watch out#

WARNING

fontWeight에 해당하는 폰트 파일이 없으면 Flutter가 가장 가까운 웨이트를 합성(simulate)합니다.
합성된 폰트는 품질이 떨어지므로 필요한 웨이트의 파일을 모두 포함하세요.

# 잘못된 예시 - w500 요청 시 w400을 합성
flutter:
fonts:
- family: MyFont
fonts:
- asset: fonts/MyFont-Regular.ttf # w400만 있음
# 올바른 예시 - 필요한 웨이트 모두 포함
flutter:
fonts:
- family: MyFont
fonts:
- asset: fonts/MyFont-Regular.ttf
weight: 400
- asset: fonts/MyFont-Medium.ttf
weight: 500
- asset: fonts/MyFont-Bold.ttf
weight: 700

결론: pubspec.yaml에 폰트 파일을 등록하고 weight 속성으로 웨이트를 매핑합니다.


챕터 2: Google Fonts 패키지 사용하기#

Why#

NOTE

폰트 파일을 직접 관리하는 것은 번거롭습니다.
google_fonts 패키지를 사용하면 1,000개 이상의 오픈소스 폰트를 네트워크에서 자동으로 다운로드해서 사용할 수 있습니다.

graph LR A[GoogleFonts.lato] --> B[네트워크 다운로드] B --> C[로컬 캐시 저장] C --> D[TextStyle 반환]

What#

NOTE

google_fonts 패키지는 Google Fonts에서 제공하는 모든 폰트를 런타임에 다운로드하고 캐싱합니다.
폰트 파일을 프로젝트에 포함할 필요가 없어 앱 크기를 줄일 수 있습니다.

How#

TIP

패키지 설치

Terminal window
flutter pub add google_fonts

기본 사용법

import 'package:google_fonts/google_fonts.dart';
Text(
'Hello, Google Fonts!',
style: GoogleFonts.lato(),
)

스타일 커스터마이징

Text(
'커스텀 스타일',
style: GoogleFonts.notoSansKr(
fontSize: 24,
fontWeight: FontWeight.w700,
fontStyle: FontStyle.normal,
letterSpacing: -0.5,
),
)

기존 TextStyle과 결합

Text(
'테마 스타일과 결합',
style: GoogleFonts.lato(
textStyle: Theme.of(context).textTheme.displayLarge,
fontWeight: FontWeight.bold,
),
)

동적으로 폰트 선택

// 폰트 이름을 문자열로 지정
Text(
'동적 폰트',
style: GoogleFonts.getFont('Noto Sans KR'),
)

앱 전체 기본 폰트로 설정

MaterialApp(
theme: ThemeData(
textTheme: GoogleFonts.notoSansKrTextTheme(),
),
)

Watch out#

WARNING

Google Fonts는 첫 사용 시 네트워크에서 다운로드합니다.
오프라인 환경에서는 미리 캐시되지 않은 폰트를 사용할 수 없습니다.

// 프로덕션에서는 폰트를 미리 번들링하는 것을 권장
// pubspec.yaml에 에셋으로 폰트 파일 포함 후:
GoogleFonts.config.allowRuntimeFetching = false;

앱 크기가 중요하다면 Google Fonts를, 오프라인 지원이 중요하다면 로컬 폰트를 선택하세요.

결론: google_fonts 패키지로 다양한 폰트를 쉽게 사용할 수 있지만, 오프라인 지원이 필요하면 로컬 폰트를 사용합니다.


챕터 3: TextTheme으로 타이포그래피 시스템 구축#

Why#

NOTE

앱 전체에서 일관된 텍스트 스타일을 유지하려면 체계적인 타이포그래피 시스템이 필요합니다.
TextTheme은 15가지 텍스트 스타일을 정의해서 용도별로 사용할 수 있게 합니다.

displayLarge/Medium/Small → 큰 제목
headlineLarge/Medium/Small → 섹션 제목
titleLarge/Medium/Small → 카드 제목 등
bodyLarge/Medium/Small → 본문
labelLarge/Medium/Small → 버튼, 레이블

What#

NOTE

Material Design 3의 TextTheme은 5개 카테고리(Display, Headline, Title, Body, Label)와 3개 크기(Large, Medium, Small)로 구성됩니다.
총 15가지 스타일 조합을 제공합니다.

How#

TIP

커스텀 TextTheme 정의

MaterialApp(
theme: ThemeData(
textTheme: const TextTheme(
displayLarge: TextStyle(
fontSize: 57,
fontWeight: FontWeight.w400,
letterSpacing: -0.25,
height: 1.12,
),
displayMedium: TextStyle(
fontSize: 45,
fontWeight: FontWeight.w400,
letterSpacing: 0,
height: 1.16,
),
displaySmall: TextStyle(
fontSize: 36,
fontWeight: FontWeight.w400,
letterSpacing: 0,
height: 1.22,
),
headlineLarge: TextStyle(
fontSize: 32,
fontWeight: FontWeight.w600,
letterSpacing: 0,
height: 1.25,
),
headlineMedium: TextStyle(
fontSize: 28,
fontWeight: FontWeight.w600,
letterSpacing: 0,
height: 1.29,
),
headlineSmall: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
letterSpacing: 0,
height: 1.33,
),
titleLarge: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w500,
letterSpacing: 0,
height: 1.27,
),
titleMedium: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
letterSpacing: 0.15,
height: 1.50,
),
titleSmall: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
letterSpacing: 0.1,
height: 1.43,
),
bodyLarge: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
letterSpacing: 0.5,
height: 1.50,
),
bodyMedium: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
letterSpacing: 0.25,
height: 1.43,
),
bodySmall: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
letterSpacing: 0.4,
height: 1.33,
),
labelLarge: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
letterSpacing: 0.1,
height: 1.43,
),
labelMedium: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
letterSpacing: 0.5,
height: 1.33,
),
labelSmall: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w500,
letterSpacing: 0.5,
height: 1.45,
),
),
),
)

위젯에서 TextTheme 사용

class ArticlePage extends StatelessWidget {
const ArticlePage({super.key});
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Flutter 튜토리얼', style: textTheme.displaySmall),
const SizedBox(height: 8),
Text('커스텀 폰트 가이드', style: textTheme.headlineMedium),
const SizedBox(height: 16),
Text(
'이 문서에서는 Flutter에서 커스텀 폰트를 사용하는 방법을 설명합니다.',
style: textTheme.bodyLarge,
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {},
child: Text('시작하기', style: textTheme.labelLarge),
),
],
);
}
}

Watch out#

WARNING

TextTheme의 모든 스타일을 사용할 필요는 없습니다.
앱에 필요한 스타일만 정의하고 일관되게 사용하세요.

// 권장: 용도에 맞는 스타일 사용
Text('페이지 제목', style: textTheme.headlineLarge)
Text('본문 내용', style: textTheme.bodyMedium)
// 비권장: 임의로 폰트 크기 지정
Text('페이지 제목', style: TextStyle(fontSize: 28))
Text('본문 내용', style: TextStyle(fontSize: 14))

결론: TextTheme으로 타이포그래피를 체계화하면 앱 전체에서 일관된 텍스트 스타일을 유지할 수 있습니다.


챕터 4: Google Fonts로 TextTheme 생성#

Why#

NOTE

Google Fonts와 TextTheme을 결합하면 커스텀 폰트로 전체 타이포그래피 시스템을 쉽게 구축할 수 있습니다.
GoogleFonts.xxxTextTheme() 메서드가 이 작업을 간단하게 만들어줍니다.

GoogleFonts.notoSansKrTextTheme() → 15가지 스타일 모두 Noto Sans KR 적용

What#

NOTE

각 Google Font에는 해당 폰트가 적용된 전체 TextTheme을 반환하는 메서드가 있습니다.
기존 TextTheme을 기반으로 폰트만 교체할 수도 있습니다.

How#

TIP

전체 TextTheme을 Google Font로 설정

MaterialApp(
theme: ThemeData(
textTheme: GoogleFonts.notoSansKrTextTheme(),
),
)

기존 테마에 Google Font 병합

MaterialApp(
theme: ThemeData(
textTheme: GoogleFonts.notoSansKrTextTheme(
Theme.of(context).textTheme,
),
),
)

제목과 본문에 다른 폰트 적용

final baseTextTheme = Theme.of(context).textTheme;
MaterialApp(
theme: ThemeData(
textTheme: TextTheme(
// Display, Headline은 Playfair Display
displayLarge: GoogleFonts.playfairDisplay(
textStyle: baseTextTheme.displayLarge,
),
displayMedium: GoogleFonts.playfairDisplay(
textStyle: baseTextTheme.displayMedium,
),
headlineLarge: GoogleFonts.playfairDisplay(
textStyle: baseTextTheme.headlineLarge,
),
// Body, Label은 Noto Sans
bodyLarge: GoogleFonts.notoSans(
textStyle: baseTextTheme.bodyLarge,
),
bodyMedium: GoogleFonts.notoSans(
textStyle: baseTextTheme.bodyMedium,
),
labelLarge: GoogleFonts.notoSans(
textStyle: baseTextTheme.labelLarge,
),
),
),
)

다크 모드용 TextTheme

ThemeData(
brightness: Brightness.dark,
textTheme: GoogleFonts.notoSansKrTextTheme(
ThemeData.dark().textTheme,
),
)

Watch out#

WARNING

GoogleFonts.xxxTextTheme()은 기본 Material TextTheme을 사용합니다.
커스텀 크기나 높이가 필요하면 별도로 정의해야 합니다.

// 기본 크기 사용
GoogleFonts.latoTextTheme()
// 커스텀 크기 필요 시
GoogleFonts.latoTextTheme().copyWith(
displayLarge: GoogleFonts.lato(
fontSize: 60, // 기본값 57 대신 60
fontWeight: FontWeight.bold,
),
)

결론: GoogleFonts.xxxTextTheme()으로 전체 타이포그래피 시스템에 Google Font를 쉽게 적용할 수 있습니다.


챕터 5: 폰트 웨이트와 스타일 활용#

Why#

NOTE

같은 폰트라도 웨이트(굵기)와 스타일(이탤릭)을 조합하면 다양한 표현이 가능합니다.
적절한 웨이트 활용은 시각적 계층 구조를 만드는 데 중요합니다.

본문: w400 (Regular)
강조: w500 (Medium) 또는 w600 (SemiBold)
제목: w700 (Bold)

What#

NOTE

FontWeight 클래스는 100부터 900까지 9단계 웨이트를 제공합니다.
FontStylenormalitalic 두 가지를 지원합니다.

How#

TIP

FontWeight 상수

FontWeight.w100 // Thin
FontWeight.w200 // ExtraLight
FontWeight.w300 // Light
FontWeight.w400 // Regular (기본값)
FontWeight.w500 // Medium
FontWeight.w600 // SemiBold
FontWeight.w700 // Bold
FontWeight.w800 // ExtraBold
FontWeight.w900 // Black
// 별칭
FontWeight.normal // w400
FontWeight.bold // w700

웨이트와 스타일 조합

// Regular
TextStyle(fontWeight: FontWeight.w400)
// Bold
TextStyle(fontWeight: FontWeight.w700)
// Italic
TextStyle(fontStyle: FontStyle.italic)
// Bold Italic
TextStyle(
fontWeight: FontWeight.w700,
fontStyle: FontStyle.italic,
)

시각적 계층 예시

class Typography extends StatelessWidget {
const Typography({super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 메인 제목 - ExtraBold
Text(
'Welcome',
style: GoogleFonts.notoSansKr(
fontSize: 32,
fontWeight: FontWeight.w800,
),
),
// 부제목 - SemiBold
Text(
'시작해봅시다',
style: GoogleFonts.notoSansKr(
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
// 본문 - Regular
Text(
'이 앱은 Flutter로 만들어졌습니다. 다양한 기능을 제공합니다.',
style: GoogleFonts.notoSansKr(
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
// 캡션 - Light
Text(
'버전 1.0.0',
style: GoogleFonts.notoSansKr(
fontSize: 12,
fontWeight: FontWeight.w300,
color: Colors.grey,
),
),
],
);
}
}

Watch out#

WARNING

모든 폰트가 9단계 웨이트를 지원하지는 않습니다.
폰트가 지원하지 않는 웨이트를 요청하면 가장 가까운 웨이트로 대체됩니다.

// Roboto는 9단계 모두 지원
GoogleFonts.roboto(fontWeight: FontWeight.w500) // Medium 지원
// 일부 폰트는 제한된 웨이트만 지원
// 예: w400, w700만 있는 폰트에서 w500 요청 시 w400으로 대체

Google Fonts 웹사이트에서 각 폰트의 지원 웨이트를 확인하세요.

결론: 폰트 웨이트를 적절히 활용해서 텍스트의 시각적 계층 구조를 만듭니다.


챕터 6: 가변 폰트와 고급 기능#

Why#

NOTE

가변 폰트(Variable Font)는 하나의 파일에 여러 웨이트와 스타일을 포함합니다.
FontVariation을 사용하면 웨이트를 연속적인 값으로 지정할 수 있습니다.

일반 폰트: w400, w500, w600 등 불연속 값
가변 폰트: 100~900 사이의 모든 값 (예: w450)

What#

NOTE

가변 폰트는 OpenType 규격의 디자인 축(axis)을 지원합니다.
웨이트(wght), 너비(wdth), 기울기(slnt) 등을 연속적으로 조절할 수 있습니다.

How#

TIP

FontVariation 사용

Text(
'가변 폰트 텍스트',
style: TextStyle(
fontFamily: 'RobotoFlex', // 가변 폰트
fontVariations: const [
FontVariation('wght', 450), // 웨이트 450 (불연속 값 사이)
FontVariation('wdth', 80), // 너비 80%
FontVariation('slnt', -12), // 기울기 -12도
],
),
)

가변 폰트 등록 (pubspec.yaml)

flutter:
fonts:
- family: RobotoFlex
fonts:
- asset: fonts/RobotoFlex-VariableFont.ttf

FontFeature로 글리프 선택

Text(
'fi fl', // 합자(ligature) 테스트
style: TextStyle(
fontFamily: 'Fira Code',
fontFeatures: const [
FontFeature.enable('liga'), // 합자 활성화
FontFeature.enable('calt'), // 문맥 대체 활성화
],
),
)

숫자 스타일 변경

Text(
'0123456789',
style: TextStyle(
fontFeatures: const [
FontFeature.oldstyleFigures(), // 올드스타일 숫자
// 또는
FontFeature.tabularFigures(), // 등폭 숫자
],
),
)

Watch out#

WARNING

모든 폰트가 FontFeature와 FontVariation을 지원하지는 않습니다.
폰트 파일이 해당 기능을 포함해야 작동합니다.

// 지원하지 않는 기능을 설정해도 에러는 없지만 효과도 없음
Text(
'Text',
style: TextStyle(
fontFamily: 'BasicFont', // FontFeature 미지원 폰트
fontFeatures: [FontFeature.enable('smcp')], // 무시됨
),
)

가변 폰트를 사용하기 전에 해당 폰트의 지원 축(axis)을 확인하세요.

결론: 가변 폰트와 FontFeature를 활용하면 더 세밀한 타이포그래피 제어가 가능합니다.


한계#

Flutter의 폰트 시스템은 대부분의 상황을 처리하지만 몇 가지 제약이 있습니다.

  • 웹 포맷 미지원: .woff, .woff2는 데스크톱 플랫폼에서 사용할 수 없습니다.
  • 일부 OpenType 기능 미지원: 모든 OpenType 기능이 Flutter에서 작동하지는 않습니다.
  • 폰트 합성 품질: 존재하지 않는 웨이트를 합성하면 품질이 저하됩니다.
  • CJK 폰트 크기: 한중일 폰트는 파일 크기가 커서 앱 용량에 영향을 줍니다.

Footnotes#

  1. google_fonts: Google Fonts에서 제공하는 1,000개 이상의 오픈소스 폰트를 Flutter에서 사용할 수 있게 해주는 패키지다.

  2. TextTheme: Material Design의 타이포그래피 시스템으로, 15가지 텍스트 스타일(displayLarge~labelSmall)을 정의한다.

공유

이 글이 도움이 되었다면 다른 사람과 공유해주세요!

Flutter 튜토리얼 23편: 커스텀 폰트와 타이포그래피
https://moodturnpost.net/posts/flutter/flutter-fonts-typography/
작성자
Moodturn
게시일
2026-01-08
Moodturn

목차