1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
게시판

07 / 14

CSS 기본

이제 옷을 입혀요. 두 가지를 해야 해요. 서버가 static 폴더를 열어 주기, HTML이 CSS 파일을 불러오기.

app.py에 두 줄 추가해요. import 한 줄, mount 한 줄.

app.py
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")

app.mount("/static", ...) /static/... 주소로 오면 static 폴더의 파일을 그대로 보내라는 뜻이에요. 이미지도 CSS도 JS도 이 폴더에 둬요.

templates/index.html의 <head> 안에 한 줄 추가해요.

templates/index.html
  <title>crack.log</title>
  <link rel="stylesheet" href="/static/style.css">

실행하면 이미지부터 떠요. 이제 static/style.css 탭을 채워요.

static/style.css
:root {
  --bg: #f8f9fa;
  --panel: #ffffff;
  --text: #212529;
  --muted: #868e96;
  --line: #e9ecef;
  --mint: #12b886;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, "Segoe UI", "Noto Sans KR", "Malgun Gothic", sans-serif;
  background: var(--bg);
  color: var(--text);
}

header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 32px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}

header a {
  font-size: 22px;
  font-weight: 800;
  color: var(--mint);
  text-decoration: none;
}

main {
  max-width: 1000px;
  margin: 0 auto;
  padding: 32px;
}

h1 { font-size: 22px; }

.card {
  max-width: 320px;
  background: var(--panel);
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
}

.card a { color: inherit; text-decoration: none; }
.card .thumb { display: block; width: 100%; height: 160px; object-fit: cover; }
.card .body { padding: 16px; }
.card h2 { margin: 0 0 8px; font-size: 16px; }
.card p { margin: 0 0 12px; font-size: 14px; line-height: 1.5; color: var(--muted); }
.card .date { font-size: 12px; color: var(--muted); }

.card .foot {
  display: flex;
  justify-content: space-between;
  padding: 10px 16px;
  border-top: 1px solid var(--line);
  font-size: 12px;
  color: var(--muted);
}

길어 보이지만 구조는 하나예요. 누구를(선택자) 어떻게(속성: 값;).

  • body 태그 이름으로 고르기. 모든 <body>.
  • .card 점으로 시작하면 class 이름으로 고르기. class="card"인 것들.
  • .card h2 띄어쓰기는 "안에 있는". card 안의 h2.
  • :root에 --mint 같은 변수를 모아 두고 var(--mint)로 꺼내 써요. 색을 한 곳에서 바꾸려고요. 9단계 다크모드에서 빛을 봐요.
  • header의 display: flex는 안의 것들을 가로로 나란히. justify-content: space-between은 양 끝으로 밀기.

해보기

--mint 값을 다른 색으로 바꿔 보세요. #f783ac 같은 거요. 카드의 border-radius를 24px로 바꿔 보세요.

이 단계 전체 코드
app.py
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")


@app.get("/", response_class=HTMLResponse)
def home(request: Request):
    return templates.TemplateResponse(request, "index.html")
templates/index.html
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>crack.log</title>
  <link rel="stylesheet" href="/static/style.css">
</head>
<body>
  <header>
    <a href="/">crack.log</a>
  </header>
  <main>
    <h1>내 블로그</h1>
    <article class="card">
      <a href="/posts/1">
        <img class="thumb" src="/static/thumb1.svg" alt="">
        <div class="body">
          <h2>FastAPI 첫 서버 띄우기</h2>
          <p>uvicorn 한 줄이면 끝. 흰 화면에 Hello World가 뜨는 순간부터 시작.</p>
          <span class="date">2026년 10월 10일</span>
        </div>
      </a>
      <div class="foot">
        <span>by <b>sfadfe</b></span>
        <span>♥ 518</span>
      </div>
    </article>
  </main>
</body>
</html>
static/style.css
:root {
  --bg: #f8f9fa;
  --panel: #ffffff;
  --text: #212529;
  --muted: #868e96;
  --line: #e9ecef;
  --mint: #12b886;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, "Segoe UI", "Noto Sans KR", "Malgun Gothic", sans-serif;
  background: var(--bg);
  color: var(--text);
}

header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 32px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}

header a {
  font-size: 22px;
  font-weight: 800;
  color: var(--mint);
  text-decoration: none;
}

main {
  max-width: 1000px;
  margin: 0 auto;
  padding: 32px;
}

h1 { font-size: 22px; }

.card {
  max-width: 320px;
  background: var(--panel);
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
}

.card a { color: inherit; text-decoration: none; }
.card .thumb { display: block; width: 100%; height: 160px; object-fit: cover; }
.card .body { padding: 16px; }
.card h2 { margin: 0 0 8px; font-size: 16px; }
.card p { margin: 0 0 12px; font-size: 14px; line-height: 1.5; color: var(--muted); }
.card .date { font-size: 12px; color: var(--muted); }

.card .foot {
  display: flex;
  justify-content: space-between;
  padding: 10px 16px;
  border-top: 1px solid var(--line);
  font-size: 12px;
  color: var(--muted);
}
준비
localhost:8000 ↗