일반 폼 안에서 미리보기처럼 제출이 목적이 아닌 버튼은 type="button"으로 구분하세요. 폼 제출을 계기로 입력값을 처리하고 기본 페이지 이동은 막으려면, type="submit"을 두고 폼의 submit 이벤트에서 event.preventDefault()를 호출합니다.
AI가 만든 폼도 버튼 역할부터 나눠 보세요. 미리보기를 고치려고 제출용 버튼까지 모두 type="button"으로 바꿀 필요는 없습니다.
먼저 버튼의 목적을 나눠 보세요
- 미리보기·패널 열기 같은 화면 조작:
type="button". 클릭 처리는 연결하되 버튼 자체의 폼 제출 동작은 쓰지 않습니다. - 폼 입력을 받아 처리하기:
type="submit". 기본 이동을 원하지 않는다면 폼의submit이벤트를 처리합니다.

click만 연결했는데 submit도 기록되는 예제
아래는 네트워크 요청 없이 이벤트 순서만 보는 합성 예제입니다. 폼의 submit 이벤트를 일부러 취소해 페이지를 이동시키지 않고, 발생한 이벤트 이름을 화면에 쌓습니다.
<form id="demo-form">
<button id="preview">미리보기</button>
</form>
<pre id="log"></pre>
<script>
const log = document.querySelector("#log");
document.querySelector("#preview").addEventListener("click", () => {
log.textContent += "click\n";
});
document.querySelector("#demo-form").addEventListener("submit", (event) => {
event.preventDefault();
log.textContent += "submit\n";
});
</script>
미리보기를 한 번 클릭하면 click 다음 줄에 submit이 기록됩니다. 이 코드의 클릭 리스너는 글자를 추가할 뿐, 버튼의 기본 폼 제출을 취소하지 않습니다. 기본 이동을 막는 것은 아래쪽 submit 리스너 안의 event.preventDefault()입니다.
WHATWG의 button 정의에서 type 생략은 Auto 상태입니다. 위 예제처럼 command·commandfor가 없고, 부모가 select가 아닌 버튼은 제출 버튼에 해당합니다. 폼에 속한 이 보통 버튼의 동작을, 특수 속성을 가진 모든 버튼으로 넓혀 말하지는 않겠습니다.
미리보기라면 버튼 한 줄만 바꿉니다
<button id="preview" type="button">미리보기</button>
앞 예제의 버튼 줄만 이 코드로 교체하고 같은 리스너를 두면, 한 번 클릭했을 때 click만 기록됩니다. submit 이벤트는 따라오지 않았습니다. type="button"이 클릭 이벤트까지 없애는 것은 아닙니다.

입력 처리가 목적이면 submit을 남깁니다
다음은 별도의 완전한 예제입니다. 합성 이름 sample을 입력한 폼을 제출하고, 기본 이동은 취소한 뒤 FormData로 값을 읽어 화면에 표시합니다. 서버 저장 코드는 없습니다.
<form id="sample-form">
<label>샘플 이름 <input name="name" value="sample" required></label>
<button type="submit">내용 확인</button>
</form>
<p id="result">대기</p>
<script>
const form = document.querySelector("#sample-form");
form.addEventListener("submit", (event) => {
event.preventDefault();
const data = new FormData(form);
document.querySelector("#result").textContent = data.get("name");
});
</script>
내용 확인을 누르면 대기가 sample로 바뀝니다. 이름을 비운 채 누르면 이 예제의 required 검증이 먼저 막고 브라우저가 입력 안내를 표시합니다. WHATWG의 폼 제출 알고리즘도 이 경로에서 제약 검증 뒤 취소 가능한 submit 이벤트를 발생시키고, 취소됐다면 제출 절차를 멈추도록 정합니다.
preventDefault()가 값을 저장하거나 서버로 보내 주는 것은 아닙니다. 이 예제에서는 값을 읽어 문단만 바꿉니다. 폼의 method를 바꾸는 코드도 아니므로, type="button"으로 바꿨더니 POST가 GET으로 전환됐다고 해석하면 다른 이동 코드를 놓칠 수 있습니다.

수정 뒤에도 페이지가 이동한다면 클릭 리스너에 별도의 이동 코드가 있는지 확인하세요. 버튼 타입 하나가 모든 이동 원인을 차단하는 것은 아닙니다. 제출 처리를 JavaScript 요청으로 바꾼 뒤 응답 오류를 구분해야 한다면 fetch의 HTTP 오류와 Promise 거부를 구분한 글이 다음 질문에 답합니다.
2026년 10월 25일 Windows x64의 Aside 브라우저에서 위 코드로 click·submit 기록, 합성 입력값 읽기, required 빈칸 안내를 확인했습니다. User-Agent가 보고한 Chrome 버전은 154.0.0.0입니다. 외부 네트워크 요청은 하지 않았고 실제 서버 저장·페이지 새로고침이나 React 등 프레임워크의 동작을 시험한 결과는 아닙니다.
출처 및 확인일
- WHATWG, HTML Standard — 4.10.6 The button element. Living Standard, 해당 조항의 별도 게시·수정일 미표기. 확인: 2026-10-25 KST.
- WHATWG, HTML Standard — 4.10.22.3 Form submission algorithm. Living Standard, 해당 조항의 별도 게시·수정일 미표기. 확인: 2026-10-25 KST.
'💻IT' 카테고리의 다른 글
| JavaScript forEach에 await 안 먹히는 이유와 비동기 해결법 (for...of) (0) | 2026.11.01 |
|---|---|
| JavaScript sort() 숫자 정렬 오류 해결법 (10이 2보다 앞에 올 때) (0) | 2026.10.31 |
| Flex 카드 말줄임이 안 되면, 제목을 감싼 요소부터 보세요 (0) | 2026.10.26 |
| replaceAll을 썼는데 그대로라면, 반환값부터 보세요 (0) | 2026.10.25 |
| 객체를 복사했는데 원본도 바뀌나요? JavaScript 전개 구문의 경계 (0) | 2026.10.25 |
