Address Field
공통 컴포넌트사용자 화면과 관리자 화면 모두에서 쓰입니다.
주소 검색 버튼으로 우편번호와 주소를 함께 입력하는 두 가지 주소 입력 방식입니다.
[주소 검색] 버튼으로 검색해서 주소를 고르면 우편번호와 주소가 함께 채워지고, 상세 주소만 직접 입력하는 컴포넌트입니다. 서비스에 맞게 입력 방식 2가지와 검색 방식 2가지를 고를 수 있습니다.
주소 검색 버튼으로 시작합니다.Input types
| 타입 | 구성 | 이럴 때 |
|---|---|---|
"basic" | 주소 · 상세 주소 | 우편번호를 따로 저장하지 않아도 되는 서비스 |
"postcode" | 우편번호 · 주소 · 상세 주소 | 배송, 결제처럼 우편번호가 필요한 서비스 |
Type A — basic
주소 입력칸 옆의 주소 검색을 눌러 검색합니다. 아래에 상세 주소칸이 이어집니다.
현재 값: {"postcode":"","address":"","address_detail":""}
<AddressField
variant="basic"
searchMode="dialog"
value={value}
onChange={setValue}
/>Type B — postcode
우편번호칸과 검색 버튼이 위에, 그 아래 주소칸, 상세 주소칸 순서입니다. 검색 결과를 고르면 우편번호와 주소가 한 번에 들어갑니다.
현재 값: {"postcode":"","address":"","address_detail":""}
<AddressField
variant="postcode"
searchMode="dialog"
value={value}
onChange={setValue}
/>테헤란로, 봉은사로 같은 도로명이나 미어캣타워 같은 건물명으로 검색하면 샘플 주소 150건 중에서 찾아 줍니다. 아래 "검색 창" 섹션에서 직접 해 보세요.Search modes
주소를 어디서 어떻게 검색할지 두 가지 중에 고릅니다. signup.addressSearch 설정 또는 searchMode prop으로 정합니다.
| searchMode | 무엇이 열리나 | 서버 | 결과가 많을 때 |
|---|---|---|---|
"popup" (기본) | 카카오(다음) 우편번호 팝업 | 필요 없음 | 팝업이 알아서 목록을 나눠 보여 줍니다. |
"dialog" | 우리 검색 창 (검색어 입력 → 결과 목록) | 필요함 (GET /address/search) | 10건씩 보여 주고 [더 보기]로 이어서 불러옵니다. |
export default defineMeerkitConfig({
signup: {
optionalFields: ["address"],
addressType: "postcode", // 입력 방식
addressSearch: "dialog", // 검색 방식: "popup" | "dialog"
},
});Search dialog
dialog 방식은 검색어를 입력하면 결과 목록이 나옵니다. 아래에서 직접 검색해 보세요. (서울처럼 넓게 검색하면 150건이 나와 더 보기를 쓸 수 있고, 오류를 검색하면 오류 화면을, 없는 단어를 검색하면 결과 없음 화면을 볼 수 있습니다.)
<AddressField
variant="postcode"
searchMode="dialog"
searchProvider={mySearch} // 생략하면 서비스 서버의 GET /address/search
value={value}
onChange={setValue}
/>화면 동작
| 상황 | 화면 |
|---|---|
| 처음 열었을 때 | "주소를 검색해 보세요" 안내와 검색 예시 |
| 검색 중 | "검색 중…" 표시 (버튼 잠김) |
| 결과 있음 | "검색 결과 N건"과 목록. 각 줄에 도로명 주소, 우편번호, 지번 주소(건물명) |
| 결과가 많음 | 10건씩 보여 주고 [더 보기]로 이어 붙임. "N건 표시 중"으로 진행 상황 표시. 30건이 넘으면 번지나 건물명을 더해 보라는 안내 |
| 모두 불러옴 | "모든 결과를 불러왔습니다." |
| 결과 없음 | "…와 맞는 주소가 없습니다"와 다시 검색 안내 |
| 서버 오류 | 오류 문구와 [다시 시도] 버튼. 더 보기에서 실패하면 이미 불러온 목록은 그대로 두고 오류만 표시 |
| 주소를 고름 | 우편번호와 주소가 입력칸에 채워지고 창이 닫힘. 이어서 상세 주소 입력 |
Many results
검색 결과가 수백 건이면 한 번에 모두 보여 줄 수 없으니, 한 쪽씩 불러오고 검색어를 좁히게 안내하는 방식을 씁니다.
- 한 번에 10건만 요청하고, [더 보기]를 누르면 다음 쪽을 이어서 붙입니다. 화면이 길어지지 않도록 목록 영역은 높이가 제한되고 안에서 스크롤됩니다.
- 입력한 글자마다 요청하지 않고, 검색 버튼이나 Enter를 눌렀을 때만 요청합니다.
- 결과가 아주 많으면 "번지나 건물명을 더하면 더 빨리 찾을 수 있어요" 안내를 보여 줍니다. 카카오 로컬 API처럼 가져올 수 있는 개수·쪽수에 상한이 있는 서비스에서는 전부 볼 수 없으므로 검색어를 좁히는 것이 가장 좋은 해결책입니다.
popup방식(카카오 우편번호 팝업)은 이 모든 것을 팝업이 알아서 처리하므로 서버 작업이 필요 없습니다.
Kakao address API (server)
dialog 방식은 서비스 서버의 GET /address/search를 호출합니다. 서버가 카카오 로컬 API(주소 검색)를 대신 불러서 아래 모양으로 돌려주면 됩니다. 카카오 REST API 키가 브라우저에 노출되지 않도록 반드시 서버를 거치세요.
GET /address/search?q=테헤란로&page=1&size=10
// response 200
{
"items": [
{
"postcode": "06236",
"address": "서울특별시 강남구 테헤란로 152",
"jibun_address": "서울특별시 강남구 역삼동 737",
"building_name": "강남파이낸스센터"
}
],
"total": 87,
"is_end": false
}import httpx
from fastapi import APIRouter
router = APIRouter()
@router.get("/address/search")
async def search_address(q: str, page: int = 1, size: int = 10):
async with httpx.AsyncClient() as client:
res = await client.get(
"https://dapi.kakao.com/v2/local/search/address.json",
params={"query": q, "page": page, "size": size},
headers={"Authorization": f"KakaoAK {settings.kakao_rest_api_key}"},
)
data = res.json()
items = []
for d in data["documents"]:
road = d.get("road_address") or {}
jibun = d.get("address") or {}
items.append({
"postcode": road.get("zone_no", ""),
"address": road.get("address_name") or d["address_name"],
"jibun_address": jibun.get("address_name"),
"building_name": road.get("building_name") or None,
})
meta = data["meta"]
return {"items": items, "total": meta["pageable_count"], "is_end": meta["is_end"]}- 우편번호(
zone_no)는 도로명 주소가 있을 때만 옵니다. 지번 주소만 있는 결과는postcode가 빈 문자열이 될 수 있습니다. - 카카오 API의 사용 한도와 약관,
size·page의 허용 범위는 카카오 개발자 문서에서 확인하세요. - 다른 주소 API(행정안전부 도로명주소 등)를 써도 같은 응답 모양으로 맞추면 화면은 바꿀 필요가 없습니다.
popup 방식을 쓰세요.Popup mode
기본값입니다. 주소 검색을 누르면 카카오(다음) 우편번호 팝업이 열리고, 주소를 고르면 우편번호와 주소가 함께 채워집니다. 서버나 API 키가 필요 없습니다. 스크립트를 불러오지 못하면 직접 입력으로 자동 전환되어 가입이 막히지 않습니다.
Use it in signup & profile
가입·내 정보 수정 폼에서는 설정만 바꾸면 됩니다.
| addressType | 서버로 가는 필드 |
|---|---|
basic | address, address_detail |
postcode | postcode, address, address_detail |
{
"user_id": "newbie",
"postcode": "06236",
"address": "서울특별시 강남구 테헤란로 152",
"address_detail": "12층 1201호",
...
}users 테이블에 postcode(basic은 불필요), address, address_detail 컬럼이 있어야 합니다 (Data Model 참고).Custom search
두 방식 모두 직접 만든 검색으로 바꿀 수 있습니다.
const mySearch: AddressSearchProvider = async (query, page, size) => {
const res = await fetch(`/my-api/address?q=${query}&page=${page}&size=${size}`);
const data = await res.json();
return { items: data.list, total: data.count, isEnd: data.last };
};
<AddressField searchMode="dialog" searchProvider={mySearch} ... />async function mySearch() {
const picked = await openMyAddressPopup();
return picked ? { postcode: picked.zip, address: picked.road } : null; // 닫으면 null
}
<AddressField searchMode="popup" onSearch={mySearch} ... />Props
| Prop | Type | 설명 |
|---|---|---|
value필수 | { postcode, address, address_detail } | 현재 값. 세 칸 모두 문자열입니다. |
onChange필수 | (value) => void | 검색하거나 상세 주소를 입력하면 바뀐 전체 값을 전달합니다. |
variant | "basic" | "postcode" | 입력 방식. 기본 basic. |
searchMode | "popup" | "dialog" | 검색 방식. 기본 popup. |
searchProvider | (query, page, size) => Promise<{ items, total, isEnd }> | dialog 방식의 검색 방법. 없으면 서비스 서버의 /address/search. |
onSearch | () => Promise<{ postcode, address } | null> | popup 방식의 검색 방법. 없으면 카카오 우편번호 팝업. |
idPrefix | string | 입력창 id 접두사. 한 화면에 둘 이상 둘 때 겹치지 않게 지정합니다. |
disabled | boolean | 모두 잠급니다. |
Accessibility
- 각 입력에
aria-label(우편번호, 주소, 상세 주소)이 있고, 검색창 입력에도aria-label이 있습니다. 검색 중 상태는role="status", 오류는role="alert"로 읽힙니다. - 검색 창은 Radix Dialog 기반이라 포커스가 갇히고
Esc로 닫을 수 있으며, 열리면 검색어 입력칸에 바로 포커스가 갑니다. - 검색 결과의 각 줄은 버튼이라 키보드(
Tab,Enter)로 고를 수 있습니다.
