CSS Essentials
10 properties and patterns that explain 80% of what you see on screen — what to understand, what to let AI handle.
Every rule in this guide came from the stylesheets powering this series — convQuestPosterStyles.css, convQuest1Styles.css, and the shared tnt-base-styles.css. Real rules from working pages.
CSS has hundreds of properties. These 10 cover the core mental models. Once you understand how CSS thinks about color, spacing, layout, and responsiveness, everything else is pattern recognition. That’s exactly where AI becomes useful — it fills in the patterns once you know what to ask for.
The 10 Properties
All 10 appear in the Conversation Quests Poster stylesheet. Open the poster and its stylesheet side by side while you read.
color
background-color
rgba()
Color & Background Color
color sets the text color of an element and its
children. background-color fills the element’s
box. rgba(r, g, b, a) adds an opacity channel:
a = 0 is fully transparent;
a = 1 is fully opaque. The dark-theme
pages in this series use rgba() throughout so
background layers remain partially transparent and blend with what’s behind them.
body {
background-color: #0d0818;
color: rgba(255, 255, 255, 0.88);
}
.poster-card {
background: rgba(8, 5, 20, 0.82);
}
.poster-qs li {
color: rgba(255, 255, 255, 0.87);
}
You understand: The fourth value in
rgba() is opacity. A card with
rgba(8,5,20,0.82) is 82% opaque — enough to
make text readable while letting the background image glow through.
AI handles: Choosing color values. Review AI’s contrast choices — white text on a dark background needs at least a 4.5:1 contrast ratio (WCAG AA). Ask AI to "ensure accessible contrast."
padding
margin
box-sizing
The Box Model
Every element is a box. padding is the space
inside the border between the content and the edge.
margin is the space outside the border
between this element and its neighbors. They look similar but behave completely
differently when backgrounds and borders are involved. The shorthand
padding: 2rem 2.6rem means "2rem top/bottom,
2.6rem left/right."
.poster-card {
padding: 2.6rem 2.6rem 2rem;
/* top 2.6rem, left/right 2.6rem, bottom 2rem */
}
.poster-tier {
padding: 0.9rem 1rem 0.85rem;
border: 1px solid rgba(255, 255, 255, 0.09);
border-radius: 0 10px 10px 0;
}
You understand: Padding grows the colored background; margin does not. If an element’s background seems too large or small, it’s usually a padding issue. If elements are too close or too far apart, it’s usually margin.
AI handles: All shorthand calculations. When
you say "give this card generous breathing room," AI writes appropriate
padding values.
font-family
font-size: clamp()
line-height
Typography
font-family names the typeface (or a fallback
chain). font-size: clamp(min, preferred, max)
is the modern responsive approach: never smaller than min, never
larger than max, scaling fluidly to preferred (usually a
viewport-relative value like 4vw).
line-height without a unit (e.g.,
1.45) is always relative to the current font
size — this is always the correct approach.
:root {
--playfair: 'Playfair Display', Georgia, serif;
--nunito: 'Nunito', 'Inter', Arial, sans-serif;
}
.poster-title {
font-family: var(--playfair);
font-size: clamp(1.6rem, 4vw, 2.5rem);
line-height: 1.1;
}
.poster-qs li {
font-size: 0.79rem;
line-height: 1.45;
}
You understand:
clamp() eliminates the need for a separate
media query just for font size. The three values are: minimum, preferred
(viewport-based), maximum. AI uses it correctly when you ask for
"responsive font sizing."
AI handles: Font stacks and
clamp() values. The fallback chain
(Georgia, serif) is what renders if the
Google Font fails to load — AI always includes it.
display: flex
align-items
justify-content
gap
Flexbox Layout
display: flex turns an element into a flex
container and its children into flex items. justify-content
distributes items along the main axis (horizontal by default).
align-items aligns them on the cross axis
(vertical by default). When flex-direction: column
is set, those axes flip. The #1 flexbox mistake: using
justify-content when you mean
align-items, or vice versa.
.poster-tier-header {
display: flex;
align-items: center;
gap: 0.55rem;
}
.poster-tips-bar {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.3rem 2.2rem; /* row-gap / column-gap */
}
.poster-card {
display: flex;
flex-direction: column; /* stack header, grid, tips vertically */
}
You understand: To center something both
horizontally and vertically, use
display: flex; align-items: center; justify-content: center
on the parent. This is the most commonly asked-for flexbox pattern.
AI handles: All flexbox syntax. Tell AI what you want to achieve visually ("center this element," "place these side by side with a gap") and it produces correct flexbox rules.
--variable: value
var(--variable)
CSS Custom Properties
CSS custom properties (often called "variables") store a value in one place
and reference it everywhere. Change it once in :root
and every element that uses it updates. The poster uses a two-layer pattern:
global palette tokens in :root, then
component-scoped tokens set by modifier classes — the same technique
large design systems use for theming.
/* Layer 1: global palette, defined once */
:root {
--cq-curiosity: #5B9BD5;
--cq-vision: #D4A830;
}
/* Layer 2: modifier class sets a component-scoped token */
.poster-tier--curiosity {
--tier-color: var(--cq-curiosity);
}
/* Layer 3: base rule reads the token */
.poster-tier {
border-left: 3px solid var(--tier-color, rgba(255,255,255,0.3));
}
You understand: If the same color appears
hardcoded in more than one place in AI output, ask AI to extract it into a
custom property. The second value in var(--x, fallback)
is a default used if --x isn’t set.
AI handles: Creating and consuming custom properties. Review whether AI is using them consistently — it sometimes creates variables and then uses hardcoded values elsewhere in the same file.
background: gradient url()
background-size: cover
Multi-Layer Background
The background shorthand accepts multiple
comma-separated layers: the first listed is rendered on top. In the poster,
a gradient overlay comes first so it darkens the background image just enough
for white text to remain readable — regardless of how bright the image is.
background-color at the end is the fallback
rendered if both layers fail to load.
/* Poster section: image only, solid color fallback */
#posterSection {
background: url('../images/talkingPic1AExpress.jpg')
center / cover no-repeat;
background-color: #0d0818;
}
/* Hero: gradient overlay ON TOP of image */
#hero {
background:
linear-gradient(160deg, rgba(20,8,45,0.40), rgba(8,4,22,0.20)),
url('../images/convQuestHero.png') center / cover no-repeat;
background-color: #100828;
}
You understand: First layer = top. The
overlay gradient must come before the image URL, not after. When the image
file is missing, the browser falls through to
background-color. Design for that case.
AI handles: The multi-layer syntax. Tell AI "add a semi-transparent dark gradient over the background image so white text is always readable" and it produces the correct layer order.
border
border-radius
overflow: hidden
Border, Radius & Overflow
border sets all four sides at once;
border-left overrides just the left (later
rules win). border-radius: 0 10px 10px 0
rounds only the right corners — shorthand goes clockwise: top-left,
top-right, bottom-right, bottom-left. The critical partner:
overflow: hidden makes child elements respect
the parent’s rounded corners. Without it, children can visually
escape the rounded shape.
.poster-card {
border: 1.5px solid rgba(212, 168, 48, 0.35);
border-radius: 16px;
overflow: hidden; /* keeps .poster-stripe inside the corners */
}
.poster-tier {
border: 1px solid rgba(255, 255, 255, 0.09);
border-left: 3px solid var(--tier-color); /* overrides left side */
border-radius: 0 10px 10px 0; /* right corners only */
}
You understand:
overflow: hidden is the secret ingredient
when rounded corners don’t clip children. Any time a child element
bleeds outside rounded corners, add overflow: hidden
to the parent.
AI handles: Border shorthand and radius
values. May forget overflow: hidden when you
need it — specifically ask for it when a child element should clip to
the parent’s border-radius.
transition
@keyframes
animation
Transitions & Animations
transition animates a property change
smoothly when it’s triggered externally (e.g., by JavaScript swapping
a class). animation runs independently,
triggered by adding the class that references an
@keyframes rule. The distinction: transitions
react to changes; animations run on their own. Always
prefer animating opacity and
transform — they skip layout recalculation
and run on the GPU.
/* Transition: smooth when border-color changes */
.poster-card {
transition: border-color 0.4s ease;
}
/* Animation: runs when .animating class is applied */
@keyframes cardReveal {
from { opacity: 0; transform: translateY(18px) scale(0.96); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.draw-card.animating {
animation: cardReveal 0.50s cubic-bezier(0.22, 1, 0.36, 1) both;
}
You understand:
animation-fill-mode: both keeps the element
in its final @keyframes state after the
animation ends. Without it, elements snap back to their starting position.
The shorthand value both combines
forwards and backwards.
AI handles: Writing
@keyframes and
cubic-bezier values. Tell AI the character of
the animation ("snappy," "gentle," "bouncy") and it picks appropriate easing.
position: sticky
position: absolute
z-index
The Position System
Four values, four behaviors. static (default)
follows normal flow. relative nudges the element
and creates a positioning context for absolute
children. absolute pops out of flow and
positions relative to the nearest non-static ancestor.
fixed anchors to the viewport.
sticky behaves normally until a threshold,
then sticks. The most important rule: an absolutely-positioned element needs
a position: relative parent to position correctly.
/* Bootstrap's .sticky-top applies: */
#mainNav {
position: sticky;
top: 0; /* sticks to the top of the viewport */
z-index: 1020; /* stays above scrolling content */
}
/* Absolute child inside a relative parent: */
.poster-card { position: relative; } /* creates context */
.poster-card::before {
position: absolute;
inset: 0; /* shorthand: top right bottom left = 0 */
pointer-events: none;
}
You understand: If an absolutely-positioned
element is appearing in the wrong place, check whether its parent has
position: relative. That’s the fix
90% of the time.
AI handles: The correct
position value for each use case. If the
result is wrong, describe the desired behavior ("stays at the top while
scrolling," "overlays the image") and AI will choose correctly.
@media (max-width)
@media print
Media Queries
@media (max-width: 575px) applies rules
when the screen is 575px wide or narrower. @media
(min-width: 768px) applies when 768px or wider. Bootstrap uses
min-width (mobile-first); custom overrides
often use max-width. The
@media print block applies when the user prints
— use it to strip navigation and dark backgrounds from printable pages.
!important in print rules is legitimate
because they must override screen styles.
/* Phone: tighten card padding */
@media (max-width: 575px) {
.poster-card { padding: 1.8rem 1.4rem 1.5rem; }
.poster-qs li { font-size: 0.75rem; }
}
/* Print: strip dark chrome, white background */
@media print {
#mainNav, .spark-bar, #hero, footer { display: none !important; }
body { background: white !important; color: black !important; }
.poster-card { background: white !important;
backdrop-filter: none !important; }
}
You understand: Place
@media blocks at the bottom of the CSS file
so they override the rules above. Tell AI your breakpoints explicitly
(e.g., "phone below 576px, tablet 576–768px") — without guidance AI may
use inconsistent breakpoint values throughout a file.
AI handles: Writing the correct
@media syntax. Ask specifically for
@media print if you have a page that
might be printed — AI won’t add it unless you ask.