iBetter Books
수정

Ch 03. bslib으로 테마 적용하기

기본 Shiny 앱은 회색과 흰색이 주를 이루는 다소 밋밋한 모습입니다. bslib 패키지를 쓰면 Bootstrap 테마를 코드 몇 줄로 바꿀 수 있습니다. 색상과 폰트를 세밀하게 조정하거나, 이미 완성된 Bootswatch 테마를 그대로 가져와 입힐 수도 있습니다.

bslib 설치와 기본 사용

install.packages("bslib")

bslibtheme 인수를 통해 fluidPage(), navbarPage() 등 최상위 UI 함수에 적용합니다.

library(shiny)
library(bslib)

ui <- fluidPage(
  theme = bs_theme(version = 5),  # Bootstrap 5 적용
  titlePanel("테마 적용 예제"),
  p("bslib으로 Bootstrap 5가 적용된 앱입니다.")
)

version = 5로 Bootstrap 5를 씁니다. 기본값은 Bootstrap 3이므로 명시적으로 버전을 지정하는 것이 좋습니다.

bs_theme로 색상과 폰트 커스터마이징

bs_theme()의 인수로 Bootstrap의 핵심 변수를 덮어쓸 수 있습니다.

library(shiny)
library(bslib)

my_theme <- bs_theme(
  version    = 5,
  bg         = "#FFFFFF",        # 배경색
  fg         = "#212529",        # 텍스트색
  primary    = "#0d6efd",        # 주 강조색 (버튼, 링크 등)
  secondary  = "#6c757d",        # 보조색
  success    = "#198754",        # 성공 색상
  danger     = "#dc3545",        # 위험/삭제 색상
  base_font  = font_google("Noto Sans KR"),   # 구글 폰트 사용
  heading_font = font_google("Noto Serif KR") # 제목용 폰트
)

ui <- fluidPage(
  theme = my_theme,
  titlePanel("커스텀 테마"),
  sidebarLayout(
    sidebarPanel(
      actionButton("btn1", "Primary",   class = "btn-primary"),
      actionButton("btn2", "Success",   class = "btn-success"),
      actionButton("btn3", "Danger",    class = "btn-danger")
    ),
    mainPanel(
      plotOutput("demo_plot")
    )
  )
)

server <- function(input, output, session) {
  output$demo_plot <- renderPlot({
    hist(rnorm(500), col = "#0d6efd", border = "white",
         main = "Primary 색상으로 그린 히스토그램",
         xlab = "값")
  })
}

shinyApp(ui, server)

font_google()은 구글 폰트를 자동으로 로드합니다. 인터넷 연결이 필요하므로, 오프라인 환경에서는 font_face()로 로컬 폰트를 지정합니다.

Bootswatch 테마 적용

Bootswatch(https://bootswatch.com)는 Bootstrap 기반의 무료 테마 모음입니다. bs_theme(bootswatch = "테마명")으로 바로 적용할 수 있습니다.

# 사용 가능한 Bootswatch 테마 목록 확인
bslib::bootswatch_themes()
# [1] "cerulean" "cosmo" "cyborg" "darkly" "flatly" "journal"
# [7] "litera"  "lumen"  "lux"    "materia" "minty"  "morph"
# ...

ui <- fluidPage(
  theme = bs_theme(
    version    = 5,
    bootswatch = "flatly"  # 인기 있는 플랫 디자인 테마
  ),
  titlePanel("Flatly 테마"),
  sidebarLayout(
    sidebarPanel(
      sliderInput("n", "데이터 수", 10, 200, 50),
      selectInput("dist", "분포 종류",
                  choices = c("정규" = "norm",
                              "균등" = "unif",
                              "지수" = "exp"))
    ),
    mainPanel(
      plotOutput("hist")
    )
  )
)

자주 쓰이는 Bootswatch 테마를 정리하면 다음과 같습니다.

테마명 특징
flatly 청록색 계열, 깔끔한 플랫 디자인
cosmo 모던하고 미니멀한 스타일
lux 세련된 다크 네이비 + 골드
minty 민트 초록 계열, 상쾌한 느낌
darkly 다크 모드 계열
cyborg 짙은 다크 + 강렬한 강조색
morph 부드러운 뉴모피즘 스타일

다크 모드

bs_theme()bgfg를 어두운 색으로 지정하거나, 다크 계열 Bootswatch 테마를 쓰면 다크 모드가 됩니다. darklycyborg가 대표적입니다.

dark_theme <- bs_theme(
  version = 5,
  bg      = "#1a1a2e",   # 어두운 배경
  fg      = "#eaeaea",   # 밝은 텍스트
  primary = "#e94560"    # 강렬한 포인트 색
)

ui <- fluidPage(
  theme = dark_theme,
  titlePanel("다크 모드 앱"),
  p("눈이 편안한 다크 테마입니다.")
)

실시간 테마 미리보기

개발 중에는 bs_themer()를 서버에 추가하면 앱 오른쪽에 테마 조절 패널이 뜹니다. 슬라이더와 색상 선택기로 색상, 폰트, 배경 등을 실시간으로 바꿔보고 마음에 드는 설정을 코드로 복사할 수 있습니다.

server <- function(input, output, session) {
  bs_themer()   # 개발 시에만 사용, 배포 전 제거

  # ... 나머지 서버 코드
}

bs_theme와 navbarPage 조합

navbarPage()에도 같은 방식으로 테마를 적용합니다.

ui <- navbarPage(
  title = "분석 대시보드",
  theme = bs_theme(
    version    = 5,
    bootswatch = "lux",
    primary    = "#1a73e8"
  ),
  tabPanel("홈",    p("홈 탭")),
  tabPanel("분석",  plotOutput("plot1")),
  tabPanel("보고서", tableOutput("tbl1"))
)

추가 스타일: 카드 레이아웃

bslib는 Bootstrap 5의 카드 컴포넌트도 지원합니다. card() 함수로 내용을 카드 형태의 박스에 담을 수 있습니다.

library(bslib)

ui <- fluidPage(
  theme = bs_theme(version = 5, bootswatch = "flatly"),
  layout_columns(
    col_widths = c(4, 4, 4),
    card(
      card_header("요약 통계"),
      card_body(verbatimTextOutput("stats"))
    ),
    card(
      card_header("주요 지표"),
      card_body(tableOutput("metrics"))
    ),
    card(
      card_header("차트"),
      card_body(plotOutput("chart", height = "200px"))
    )
  )
)

layout_columns()card()는 bslib 0.5 이상에서 사용 가능합니다. 대시보드를 구성할 때 fluidRow() + column() 대신 더 세련된 방법입니다.