Building skins and layouts
A skin is the look of a module, and a layout is the frame of the whole site. They are built in almost the same way; what differs is where they live and which file declares them.
| Location | Declaration file | Applies to | |
|---|---|---|---|
| Module skin | modules/{module}/skins/{skin}/ | skin.xml | Instances (mid) of that module |
| Layout | layouts/{layout}/ | conf/info.xml | The site (or assigned per mid) |
Building a module skin
To give an existing module a new look, start by copying that module's skins/default/ as a whole and renaming it. The default skin is the answer key for which template files are needed and which variables are passed down.
modules/board/skins/myskin/
├── skin.xml
├── list.html ← 목록 (+ 상세는 _read.html 포함 방식)
├── _read.html
├── _comment.html
├── write_form.html
├── css/skin.css
└── ...skin.xml
<?xml version="1.0" encoding="UTF-8"?>
<skin version="0.2">
<responsive>true</responsive>
<title xml:lang="ko">내 스킨</title>
<description xml:lang="ko">설명</description>
<version>1.0.0</version>
<date>2026-08-05</date>
<author email_address="you@example.com" link="https://example.com">
<name xml:lang="ko">제작자</name>
</author>
<extra_vars>
<var name="list_style" type="select" default="list">
<title xml:lang="ko">목록 형태</title>
<options value="list"><title xml:lang="ko">목록형</title></options>
<options value="card"><title xml:lang="ko">카드형</title></options>
</var>
</extra_vars>
</skin><responsive>true</responsive>indicates that this skin handles narrow screens too. See Responsive views and responsive display.- Skin settings declared with
<extra_vars>are read in templates as$module_info->{var_name}.
Rules to follow
- A skin only changes the screen. Keep the variables passed from the server and the form conventions the same as the default skin. If you leave out hidden form fields (module, act, mid, etc.), saving won't work.
- Parts tied to admin settings, such as board list columns (
$list_config, etc.), should follow the settings instead of being hard-coded. - We recommend inline SVG for icons instead of image files.
Building a layout
layouts/mylayout/
├── layout.html ← 뼈대. {!! $content !!} 자리에 본문이 들어감
├── conf/info.xml
├── css/layout.css
└── js/layout.jsMinimal layout.html skeleton
@version(2)
@load('css/layout.css')
@load('js/layout.js')
<div class="my-layout">
<header>
<a href="{{ getUrl('') }}">{{ $layout_info->logo_text ?: '사이트' }}</a>
<nav>
@foreach ($gnb->list ?? [] as $item)
<a href="{{ $item['href'] }}" @if (!empty($item['selected'])) class="on" @endif>{{ $item['text'] }}</a>
@endforeach
</nav>
</header>
<main>{!! $content !!}</main>
<footer>{{ $layout_info->copyright }}</footer>
</div>- Output the content with
{!! $content !!}. It must not be escaped. - Menus are passed down as
$gnb->listand so on, according to the<menus>declaration in info.xml. Each item hastext(name),href(address),selected/open(current location), andlist(submenu).
conf/info.xml
<?xml version="1.0" encoding="UTF-8"?>
<layout version="0.2">
<responsive>true</responsive>
<title xml:lang="ko">내 레이아웃</title>
<version>1.0.0</version>
<author email_address="you@example.com" link="https://example.com">
<name xml:lang="ko">제작자</name>
</author>
<menus>
<menu name="gnb" maxdepth="3" default="true">
<title xml:lang="ko">상단 메뉴</title>
</menu>
</menus>
<extra_vars>
<group>
<title xml:lang="ko">기본</title>
<var name="logo_text" type="text">
<title xml:lang="ko">로고 텍스트</title>
</var>
<var name="point_color" type="color" default="#2677e3">
<title xml:lang="ko">포인트 색상</title>
</var>
</group>
</extra_vars>
</layout>- Always put
<var>elements in extra_vars inside a<group>. The layout settings screen draws groups as tabs, so variables outside a group don't appear on screen. - Settings are read in templates as
$layout_info->{var_name}. - Declare repeating items such as sliders and banners with repeat fields.
Rules for layout CSS
- Always declare
html, body { margin: 0; background: ... }as well. If you paint the background only on the top-level div, the body's default margin and the scroll gutter stay white and look like a white border around the screen in dark mode. - If you support dark mode, collect colors in CSS variables and switch them with a
data-themeattribute orprefers-color-scheme. - If you accept an accent color setting, it's easier to maintain if you pass it down as a variable, like
style="--brand: {{ $layout_info->point_color }}", so the whole CSS follows it.
Distribution
Zip skins and layouts as a folder and register them in the store, or install them directly (upload to the corresponding path). To distribute a layout and module skins as a set, bundle them into a theme package.