Chromium 156.0.8078.74. 본문의 HTML·CSS·JavaScript를 실제 브라우저로 렌더링하고 현재 화면을 캡처했습니다. 모든 조작·레이아웃·접근성을 검증한 것은 아닙니다.
이 글의 순서 6개 단락
이번에 만들 기능
지난 단계에서는 입력한 할 일을 목록에 추가했습니다. 이번에는 새로 추가되는 각 항목에 삭제 버튼을 붙입니다. 버튼을 누르면 그 버튼이 들어 있는 항목 하나만 사라지도록 만들겠습니다.
아래 완성 예제에는 앞 단계에서 만든 입력값 검사와 항목 추가 기능도 들어 있습니다. 이전 파일이 없어도 세 파일을 같은 폴더에 저장하면 시작할 수 있습니다.
항목과 버튼 만들기
할 일을 추가할 때 li 요소와 button 요소를 만듭니다. 할 일 글자는 별도의 span에 textContent로 넣습니다. 이렇게 하면 입력한 글자가 HTML 태그로 해석되지 않고 텍스트로 표시됩니다.
const item = document.createElement('li');
const text = document.createElement('span');
const deleteButton = document.createElement('button');
text.textContent = title;
deleteButton.type = 'button';
deleteButton.textContent = '삭제';
item.append(text, deleteButton);
append()는 텍스트 요소와 버튼을 차례로 li 안에 넣습니다. 버튼의 type을 button으로 지정하면 나중에 버튼이 폼 안에 놓이더라도 기본 제출 버튼으로 동작하지 않습니다.
누른 버튼에 연결된 항목 지우기
각 항목을 만들 때 삭제 버튼에도 클릭 이벤트를 연결합니다.
deleteButton.addEventListener('click', () => {
item.remove();
});
이 함수가 사용하는 item은 해당 버튼을 만들 때 함께 만든 li입니다. 따라서 버튼을 눌렀을 때 목록 전체가 아니라 그 버튼이 속한 항목만 제거합니다. 같은 과정을 새 할 일마다 반복하므로 각 버튼이 자기 항목을 삭제합니다.
완성 코드
다음 세 파일을 같은 폴더에 저장하고 index.html을 브라우저에서 여세요. defer 덕분에 JavaScript 파일은 HTML을 읽는 동안 내려받고, 문서 분석이 끝난 뒤 실행됩니다.
index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>할 일 목록</title>
<link rel="stylesheet" href="style.css">
<script src="script.js" defer></script>
</head>
<body>
<main class="todo-app">
<h1>할 일 목록</h1>
<div class="input-row">
<label for="todo-input">할 일</label>
<input id="todo-input" type="text" placeholder="할 일을 입력하세요">
<button id="add-button" type="button">추가</button>
</div>
<p id="message" role="status"></p>
<ul id="todo-list" aria-label="할 일 목록"></ul>
</main>
</body>
</html>
style.css
body {
margin: 0;
padding: 24px;
background: #f3f6fb;
color: #172033;
font-family: sans-serif;
}
.todo-app {
max-width: 520px;
margin: 0 auto;
padding: 24px;
background: white;
border-radius: 12px;
}
.input-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
}
input {
flex: 1 1 180px;
min-width: 0;
padding: 8px;
}
button {
padding: 8px 12px;
cursor: pointer;
}
#message {
min-height: 1.5em;
color: #a22b2b;
}
#todo-list {
padding: 0;
list-style: none;
}
#todo-list li {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 0;
border-bottom: 1px solid #dce3ed;
}
#todo-list li span {
overflow-wrap: anywhere;
}
script.js
const input = document.querySelector('#todo-input');
const addButton = document.querySelector('#add-button');
const list = document.querySelector('#todo-list');
const message = document.querySelector('#message');
addButton.addEventListener('click', () => {
const title = input.value.trim();
if (title === '') {
message.textContent = '할 일을 입력해 주세요.';
return;
}
message.textContent = '';
const item = document.createElement('li');
const text = document.createElement('span');
const deleteButton = document.createElement('button');
text.textContent = title;
deleteButton.type = 'button';
deleteButton.textContent = '삭제';
deleteButton.setAttribute('aria-label', `${title} 삭제`);
deleteButton.addEventListener('click', () => {
item.remove();
});
item.append(text, deleteButton);
list.append(item);
input.value = '';
input.focus();
});
aria-label은 각 삭제 버튼에 할 일 이름을 포함한 접근 가능한 이름을 지정합니다. 화면에는 간단히 ‘삭제’가 표시되지만 보조 기술로는 어떤 항목을 삭제하는 버튼인지 구분할 수 있습니다.
아래 이미지는 할 일을 추가하기 전의 초기 화면입니다. 입력과 삭제 동작은 다음 순서에 따라 직접 확인해 보세요.

직접 확인하기
책 읽기,산책하기,물 마시기를 차례로 추가합니다.- 가운데 항목의 삭제 버튼을 누릅니다.
산책하기만 사라지고 나머지 두 항목은 남아 있어야 합니다. - 남은 항목 중 하나를 삭제한 뒤 새 항목을 추가하고, 새 항목의 삭제 버튼도 눌러 봅니다.
- 공백만 입력하고 추가 버튼을 눌러 안내 문구가 나타나는지도 확인합니다.
버튼이 동작하지 않으면 개발자 도구 콘솔의 오류를 확인하고, 세 파일이 같은 폴더에 있는지와 파일 이름이 HTML의 경로와 일치하는지 살펴보세요.
연습
같은 이름의 할 일을 두 개 추가한 뒤 한쪽의 삭제 버튼을 눌러 보세요. 이름이 같아도 클릭한 버튼이 속한 항목 하나만 사라지는지 확인합니다. 이어서 삭제 버튼의 화면 문구를 지우기로 바꿔 보고, 이벤트가 여전히 동작하는지도 살펴보세요.
다음 강의에서는 항목 수와 빈 목록 안내를 추가하고, 항목을 추가하거나 삭제할 때 화면 상태를 갱신합니다.