index.css에서 Header와 관련된 코드를 따로 Header.css에 옮김Header.jsx에서 import './Header.css'를 통해 CSS 파일 적용CSS를 여러 개의 파일로 나누고 특정 컴포넌트의 파일에 import 해도 해당 파일의 CSS 규칙들은 그들이 속하는 컴포넌트에 스코핑되지 않는다.
<p
style={{
color: "red",
textAlign: "left",
}}
>
Styling..
</p>
// AuthInputs.jsx
const emailNotValid = submitted && !enteredEmail.includes("@");
// 방법 1. 동적
return (
<input
type="email"
style={{
backgroundColor: emailNotvalid? '#fed2d2' : '#d1d5db'
}}
onChange={(event) => handleInputChange('email', event.target.value)}
/>
)
// 방법 2-1. 조건부적
return (
<input
type="email"
{/* 조건부 클래스를 적용하지 않으려면 삼항연산자를 사용하고 클래스 이름으로 undefined를 추가한다. */}
className={emailNotValid ? 'invalid' : undefined}
onChange={(event) => handleInputChange('email', event.target.value)}
/>
)
// 방법 2-2. 조건부적
return(
{/* css파일에는 .label.invalid로 수정 */}
<label className={`label ${emailNotValid ? "invalid" : ""}`}>Email</label>
)
/* Header.module.css */
.paragraph {
/* ... */
}
// Header.jsx
import classes from "./Header.module.css";
<p className={classes.paragraph}>Style..</p>;
.module.css : 기본 빌드 프로세스에 대한 신호로 볼 수 있다.paragraph도 포함되어있으나 그 외에 다른 문자/숫자가 표시되어있다. → 자동적으로 빌드 툴에 의해 생성됨.
paragraph클래스 이름은 해당 컴포넌트 파일 및 해당 컴포넌트(Header)에 대해 고유한 클래스가 되는 것이다. 다른 컴포넌트에paragraph클래스를 적용해도 헤더에 적용한 것처럼은 되지 않는다.
npm install styled-components
// AuthInputs.jsx
import { styled } from "styled-components";
const ControlContainer = styled.div`
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1.5rem;
`;
// ...
return <ControlContainer></ControlContainer>;
div를 개별 컴포넌트로 만들고 어떤 스타일이든 개발자가 적용하고 싶은 스타일을 가지는 컴포넌트로 만듦.div에 적용하고 싶은 모든 스타일을 포함.div를 자동적으로 반환하는 리액트 컴포넌트(ControlContainer).🔗 관련된 자바스크립트 문법 - tagged templates
const Label = styled.label`
display: block;
margin-bottom: 0.5rem;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: #6b7280;
`;
return (
<Label className={`label ${emailNotValid ? "invalid" : ""}`}>Email</Label>
);
styled.label은 시스템 내부에서 내장 레이블(<label>)을 생성하고 개발자가 설정하는 모든 속성(className)을 전달한다.input도 생성해보자.const Input = styled.input`
width: 100%;
padding: 0.75rem 1rem;
line-height: 1.5;
background-color: #d1d5db;
color: #374151;
border: 1px solid transparent;
border-radius: 0.25rem;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
`;
return (
<ControlContainer>
<p>
<Label className={`label ${emailNotValid ? "invalid" : ""}`}>Email</Label>
<Input
type="email"
className={emailNotValid ? "invalid" : undefined}
onChange={(event) => handleInputChange("email", event.target.value)}
/>
</p>
<p>
<Label className={`label ${emailNotValid ? "invalid" : ""}`}>
Password
</Label>
<Input
type="password"
className={passwordNotValid ? "invalid" : undefined}
onChange={(event) => handleInputChange("password", event.target.value)}
/>
</p>
</ControlContainer>
);
아래처럼 Styled Components와 바닐라 CSS를 혼합할 수도 있다.
<Label className={`label ${emailNotValid ? "invalid" : ""}`}>
그러나 일반적으로 리액트 프로젝트에서는 모든 것에 대해. 사용할 단일한 솔루션을 사용한다.
const Label = styled.label`
display: block;
margin-bottom: 0.5rem;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: ${({ invalid }) => (invalid ? "#f87171" : "#6b7280")};
`;
return <Label invalid={emailNotValid}>Email</Label>;
Label에 invalid속성 추가 → true/false로 레이블 컴포넌트의 invalid 속성에 값을 전달invalid속성을 이용하여 동적으로 스타일 변경styled.label에 삽입하는 값들은 최종적으로 styled components 패키지에 의해 정의된 label 함수에서 수집되어 실행되고, 이 함수는 styled components 패키지에서 처리된다.
- styled components 패키지는 사용자에게 props(속성)을 제공 → 동적으로 실행하기 위한 함수의 input값으로써 속성 객체를 주고 실행할 것이다.
- 해당 속성을 정의된 styled 컴포넌트에 설정된 모든 속성을 포함(ex.
invalid)

$기호를 사용한다.import { useState } from "react";
import { styled } from "styled-components";
const ControlContainer = styled.div`
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1.5rem;
`;
const Label = styled.label`
display: block;
margin-bottom: 0.5rem;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: ${({ $invalid }) => ($invalid ? "#f87171" : "#6b7280")};
`;
const Input = styled.input`
width: 100%;
padding: 0.75rem 1rem;
line-height: 1.5;
background-color: ${({ $invalid }) => ($invalid ? "#fed2d2" : "#d1d5db")};
color: ${({ $invalid }) => ($invalid ? "#ef4444" : "#374151")};
border: 1px solid ${({ $invalid }) => ($invalid ? "#f73f3f" : "transparent")};
border-radius: 0.25rem;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
`;
export default function AuthInputs() {
const [enteredEmail, setEnteredEmail] = useState("");
const [enteredPassword, setEnteredPassword] = useState("");
const [submitted, setSubmitted] = useState(false);
function handleInputChange(identifier, value) {
if (identifier === "email") {
setEnteredEmail(value);
} else {
setEnteredPassword(value);
}
}
function handleLogin() {
setSubmitted(true);
}
const emailNotValid = submitted && !enteredEmail.includes("@");
const passwordNotValid = submitted && enteredPassword.trim().length < 6;
return (
<div id="auth-inputs">
<ControlContainer>
<p>
<Label $invalid={emailNotValid}>Email</Label>
<Input
type="email"
$invalid={emailNotValid}
onChange={(event) => handleInputChange("email", event.target.value)}
/>
</p>
<p>
<Label $invalid={passwordNotValid}>Password</Label>
<Input
type="password"
$invalid={passwordNotValid}
onChange={(event) =>
handleInputChange("password", event.target.value)
}
/>
</p>
</ControlContainer>
<div className="actions">
<button type="button" className="text-button">
Create a new account
</button>
<button className="button" onClick={handleLogin}>
Sign In
</button>
</div>
</div>
);
}
import { styled } from "styled-components";
import logo from "../assets/logo.png";
// import "./Header.css";
const StyledHeader = styled.header`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 2rem;
margin-bottom: 2rem;
& img {
object-fit: contain;
margin-bottom: 2rem;
width: 11rem;
height: 11rem;
}
& h1 {
font-size: 1.5rem;
font-weight: 600;
letter-spacing: 0.4em;
text-align: center;
text-transform: uppercase;
color: #9a3412;
font-family: "Pacifico", cursive;
margin: 0;
}
& p {
text-align: center;
color: #a39191;
margin: 0;
}
@media (min-width: 768px) {
& {
margin-bottom: 4rem;
}
& h1 {
font-size: 2.25rem;
}
}
`;
export default function Header() {
return (
<StyledHeader>
<img src={logo} alt="A canvas" />
<h1>ReactArt</h1>
<p>A community of artists and art-lovers.</p>
</StyledHeader>
);
}
& : styled component에 &이하의 규칙들이 헤더 내의 어느 요소(img, h1, p)에나 영향을 미친다는 것을 보여준다.&도 header를 대신한다.다음과 같이 미디어쿼리를 작성할 수 있다.
const StyledHeader = styled.header`
@media (min-width: 768px) {
margin-bottom: 4rem;
& h1 {
font-size: 2.25rem;
}
}
`;
const Button = styled.button`
padding: 1rem 2rem;
font-weight: 600;
text-transform: uppercase;
border-radius: 0.25rem;
color: #1f2937;
background-color: #f0b322;
border-radius: 6px;
border: none;
&:hover {
background-color: #f0920e;
}
`;
return <Button onClick={handleLogin}>Sign In</Button>;
&를 이용해서 적용할 수 있다. 단, 중간에 띄어쓰기는 없어야 함.import { styled } from "styled-components";
const Button = styled.button`
padding: 1rem 2rem;
font-weight: 600;
text-transform: uppercase;
border-radius: 0.25rem;
color: #1f2937;
background-color: #f0b322;
border-radius: 6px;
border: none;
&:hover {
background-color: #f0920e;
}
`;
export default Button;
import Button from "./Button.jsx";
import { styled } from "styled-components";
const Label = styled.label`
display: block;
margin-bottom: 0.5rem;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: ${({ $invalid }) => ($invalid ? "#f87171" : "#6b7280")};
`;
const Input = styled.input`
width: 100%;
padding: 0.75rem 1rem;
line-height: 1.5;
background-color: ${({ $invalid }) => ($invalid ? "#fed2d2" : "#d1d5db")};
color: ${({ $invalid }) => ($invalid ? "#ef4444" : "#374151")};
border: 1px solid ${({ $invalid }) => ($invalid ? "#f73f3f" : "transparent")};
border-radius: 0.25rem;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
`;
export default function CustomInput({ label, invalid, ...props }) {
return (
<p>
<Label $invalid={invalid}>{label}</Label>
<Input $invalid={invalid} {...props} />
</p>
);
}
import Input from "./Input.jsx"; // CustomInput -> Input으로 부르겠다.
return (
<div id="auth-inputs">
<ControlContainer>
<Input
type="email"
label="Email"
invalid={emailNotValid}
onChange={(event) => handleInputChange("email", event.target.value)}
/>
<Input
type="password"
label="Password"
invalid={passwordNotValid}
onChange={(event) =>
handleInputChange("password", event.target.value)
}
/>
</ControlContainer>
)