28
loading...
This website collects cookies to deliver better user experience
In general, stylesheets added to the page with <link>
tend to provide the best user experience:
<Link rel="prefetch">
.# Remove all files under `app/styles`
rm -r app/styles/*
/* app/styles/global.css */
:root {
--color-foreground: hsl(0, 0%, 0%);
--color-background: hsl(0, 0%, 100%);
--color-primary: hsl(230, 100%, 50%);
--color-primary-light: hsl(230, 100%, 60%);
--color-primary-dark: hsl(230, 100%, 40%);
--color-gray-dark: hsl(0, 0%, 60%);
--color-gray-light: hsl(0, 0%, 90%);
--font-body: -apple-system, "Segoe UI", Helvetica Neue, Helvetica, Roboto,
Arial, sans-serif, system-ui, "Apple Color Emoji", "Segoe UI Emoji";
}
html {
box-sizing: border-box;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
html,
body {
padding: 0;
margin: 0;
background-color: var(--color-background);
color: var(--color-foreground);
}
body {
font-family: var(--font-body);
line-height: 1.5;
}
a {
color: var(--color-primary-light);
text-decoration: none;
}
a:hover {
color: var(--color-primary-dark);
text-decoration: underline;
}
app/root.tsx
to import the global stylesheet; then, export the style in a links
function.// app/root.tsx
import type { LinksFunction } from "remix";
import styles from "~/styles/global.css";
export const links: LinksFunction = () => {
return [
{
rel: "stylesheet",
href: styles,
},
];
};
// ...
Links
compnent.// app/root.tsx
import {/*other imports*/, Links} from "remix";
// The function containing the HTML declaration
function Document({/*props*/}) {
return (
<html lang="en">
<head>
{/* This is the magic piece */}
<Links />
{/*...*/}
</head>
{/*body content*/}
</html>
);
}
TRIVIA: We can technically put <Links/ >
anywhere inside the html
tag; however, here's a reason why you should not do it.
For now, we're not aiming to get the best design award; we'll just apply some styles for the sake of making it look different.
/* app/styles/words.css */
.words__page {
margin: 1rem;
padding: 1rem;
border: 1px solid black;
}
.words__content {
padding: 0.5rem;
border: 1px solid var(--color-gray-dark);
display: grid;
grid-template-columns: 1fr 1fr;
border-radius: 3px;
}
// app/routes/words.tsx
// ...
export default function Index() {
// ...
return (
<div className="words__page">
{/*...*/}
<div className="words-content">
{/*...*/}
<Outlet />
</div>
</div>
);
}
// app/routes/words.tsx
// ...
import type { LinksFunction } from "remix";
import styles from "~/styles/words.css";
export const links: LinksFunction = () => {
return [
{
rel: "stylesheet",
href: styles,
},
];
};
// ...
Apologies if GIFs might not be that clear.
NOTE: every child route of /words
will inherit the styles exported in app/routes/words.tsx
/add
and /edit/$id
routes use a form, let's create a shared css file.I'm too lazy to think of a great design. Let's simply add a border.
// app/styles/words/shared.css
form {
border: 1px solid var(--color-gray-dark);
padding: 0.5rem 1rem;
border-radius: 3px;
}
links
function.// app/routes/words/add.tsx
// ...
import sharedStyles from "~/styles/words/shared.css";
export const links: LinksFunction = () => [
{
rel: "stylesheet",
href: sharedStyles,
},
];
// ...
Add new word
button, the word form
will be styled as expected.Add new word
is inside a form as well, that form will also have a border./* app/styles/words/shared.css */
form.word-form {
border: 1px solid var(--color-gray-dark);
padding: 0.5rem 1rem;
border-radius: 3px;
}
// fileName=app/components/WordForm.tsx
// ...
export function WordForm({ word }: { word?: Word }) {
// ...
return (
<Form method="post" className="word-form">
{/*...*/}
</Form>
);
}
/* app/styles/words/shared.css */
form.word-form {
border: 1px solid var(--color-gray-dark);
padding: 0.5rem 1rem;
border-radius: 3px;
/* Temporary style */
background-color: red;
}
/add
to /edit/$id
, the word form
styling is removed in the head
; The reason why styles were not applied to the /edit/$id
form.app/routes/words/edit.$id.tsx
/* fileName=app/routes/words/edit.$id */
// ...
import sharedStyles from "~/styles/words/shared.css";
export const links: LinksFunction = () => [
{
rel: "stylesheet",
href: sharedStyles,
},
];
// ...
Revert changes in app/styles/words/shared.css
, app/routes/words/add.tsx
, and , app/routes/words/edit.$id.tsx
/* app/components/word-form/styles */
.word-form label {
font-size: 0.8em;
}
.word-form {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 1rem;
border: 1px solid var(--color-gray-dark);
border-radius: 0.5rem;
}
// app/components/word-form/index.tsx
import type { LinksFunction } from "remix";
import styles from "./styles.css";
export const links: LinksFunction = () => [
{
rel: "stylesheet",
href: styles,
},
];
export function WordForm({ word }: { word?: Word }) {
// ...
return (
<Form method="post" className="word-form">
{/*...*/}
</Form>
);
export const links
only applied to routes
// app/routes/words/add.tsx
import type { LinksFunction } from "remix";
import { WordForm, links as formStyles } from "~/components/word-form";
export const links: LinksFunction = () => [...formStyles()];
// ...
Apply the same thing in app/routes/words/edit.$id.tsx
Q: So how do we style a custom basic HTML element using CSS?
A: The same as for word form
. Although, we need to propagate more until the reference reaches a route
.
// app/components/basic/button/index.tsx
import React from "react";
import styles from "./styles.css";
import type { LinksFunction } from "remix";
export const links: LinksFunction = () => [
{
rel: "stylesheet",
href: styles,
},
];
type CustomButtonProps = {
color?: "primary" | "success" | "danger" | "warning" | "info";
};
export const Button = React.forwardRef<
HTMLButtonElement,
JSX.IntrinsicElements["button"] & CustomButtonProps
>(({ color, ...props }, ref) => (
<button {...props} ref={ref} custom-button={color ? color : ""} />
));
/* app/components/basic/button/styles.css */
button[custom-button] {
display: block;
height: 2rem;
color: #000000;
}
button[custom-button="primary"] {
background-color: var(--color-primary);
color: #ffffff;
}
input
, select
, and textarea
.// app/components/word-form/index.tsx
// ...
import { Input, links as inputLinks } from "../basic/input/Input";
import { Select, links as selectLinks } from "../basic/select/Select";
import { TextArea, links as textAreaLinks } from "../basic/textarea/Textarea";
import { Button, links as buttonLinks } from "../basic/button/Button";
import styles from "./styles.css";
export const links: LinksFunction = () => [
...inputLinks(),
...selectLinks(),
...textAreaLinks(),
...buttonLinks(),
{
rel: "stylesheet",
href: styles,
},
];
// ...
// app/components/word-form/index.tsx
import type { LinksFunction } from "remix";
import { useTransition, Form } from "remix";
import { Word, WordType } from "~/models/word";
import { Input, links as inputLinks } from "../basic/input/Input";
import { Select, links as selectLinks } from "../basic/select/Select";
import { TextArea, links as textAreaLinks } from "../basic/textarea/Textarea";
import { Button, links as buttonLinks } from "../basic/button/Button";
import styles from "./styles.css";
export const links: LinksFunction = () => [
...inputLinks(),
...selectLinks(),
...textAreaLinks(),
...buttonLinks(),
{
rel: "stylesheet",
href: styles,
},
];
export function WordForm({ word }: { word?: Word }) {
let transition = useTransition();
return (
<Form method="post" className="word-form">
<div>Form State: {transition.state}</div>
<div>
<label htmlFor="name">Word</label>
<Input
id="name"
name="name"
type="text"
placeholder="Word"
required
defaultValue={word?.name ?? ""}
disabled={Boolean(word?.name)}
/>
</div>
<div>
<label htmlFor="type">Type</label>
<Select
id="type"
name="type"
defaultValue={word?.type ?? WordType.NOUN}
>
<option value={WordType.NOUN}>Noun</option>
<option value={WordType.VERB}>Verb</option>
<option value={WordType.ADJECTIVE}>Adjective</option>
</Select>
</div>
<div>
{word?.sentences.map((sentence, i) => (
<SentenceField index={i + 1} sentence={sentence} key={i} />
)) ?? <SentenceField index={1} sentence={""} />}
</div>
<div>
{word?.definitions.map((definition, i) => (
<DefinitionField index={i + 1} definition={definition} key={i} />
)) ?? <DefinitionField index={1} definition={""} />}
</div>
<Button type="submit" color="primary">
Submit
</Button>
</Form>
);
}
const SentenceField = ({ index, sentence }: any) => (
<div>
<label htmlFor={`sentence.${index}`}>Sentence #{index}</label>
<TextArea
id={`sentence.${index}`}
name="sentence"
defaultValue={sentence}
placeholder={`Sentence #${index}`}
minLength={10}
/>
</div>
);
const DefinitionField = ({ index, definition }: any) => (
<div>
<label htmlFor={`definition.${index}`}>Definition #{index}</label>
<TextArea
id={`definition.${index}`}
name="definition"
defaultValue={definition}
placeholder={`Definition #${index}`}
minLength={10}
/>
</div>
);
prefers-color-scheme
to update the CSS variables when a media query is satisfied./* app/styles/dark */
:root {
--color-foreground: hsl(0, 0%, 100%);
--color-background: hsl(0, 0%, 7%);
--color-primary-dark: hsl(230, 100%, 65%);
--color-primary-light: hsl(230, 100%, 80%);
}
// app/root.tsx
// ...
import styles from "~/styles/global.css";
import darkStyles from "~/styles/dark.css";
export const links: LinksFunction = () => {
return [
{
rel: "stylesheet",
href: styles,
},
{
rel: "stylesheet",
href: darkStyles,
media: "(prefers-color-scheme: dark)",
},
];
};
/* app/styles/words-sm */
.words__content {
grid-template-columns: 1fr;
border: none;
}
.words__content form {
margin-top: 1rem;
}
// app/routes/words.tsx
import type { LinksFunction } from "remix";
import styles from "~/styles/words.css";
import smStyles from "~/styles/words-sm.css";
export const links: LinksFunction = () => [
{
rel: "stylesheet",
href: styles,
},
{
rel: "stylesheet",
href: smStyles,
media: "(max-width: 600px)",
},
];
// ...
Tailwind
or Emotion
.