Until 2023, I had never owned a passport; I still have never even been on an airplane despite having many places I want to visit.
For a few years, I used a spreadsheet in Notion to list destinations, sights, and interesting restaurants, but eventually, I decided I wanted a better way to plan my future trips; I imagined others might have as well, given the boom in travel following the COVID-19 pandemic.
I saw that as an opportunity to grow and reinforce my design skills by creating a tool for myself and others to use. It is named Chronacle since it is meant to help you chronicle your journeys around the world.
Research
As the sole designer for Chronacle, I needed to be crafty when doing needfinding and figuring out which problems needed addressing or which parts of the travel planning process could be improved; in addition to doing heuristic analyses on existing travel aggregators (Expedia, TripAdvisor, etc), a large part of my generative research involved leveraging data from a 2021 study by market research leader Mintel.
Of the 1,300+ travelers surveyed, people overwhelmingly reported booking reservations through their preferred brands' platforms, with 76% using a brand's website or mobile app (ex: Marriott Hotels) vs. an aggregator. The study mentions travel aggregators are “most competitive in activities booking,” not accommodations.
Mintel also discovered folks are taking fewer, shorter trips post-pandemic, only traveling to familiar places, and booking accommodations mere days / weeks before leaving. Travelers need helpful starting points when going somewhere new and want ideas for things to do.
Designs
As such, I decided Chronacle would not handle things like flight reservations, hotel accommodations, car rentals, etc. The reduced scope is better for engineering purposes (no need to integrate various airline/hotel platforms), but users also get a more focused and predictable experience with fewer potential failure points.
Ultimately, the main focus for Chronacle would just be helping people check out new places they're interested in visiting and save activities / experiences they want to make plans for (ex: shopping, places in nature, landmarks, etc).
Design System
Chronacle's design system is being maintained in Figma and Storybook, allowing me to keep future designs consistent by using things like design tokens, as well as make development flows simple and seamless.
Front-End Development
In addition to being Chronacle's designer, I am also developing it as a progressive web application, so lots of my design decisions are informed by my understanding of modern web's possibilities and limitations.
I wanted to make my development process as streamlined as possible, so I thought a lot about different tools that would help make that happen. In terms of front-end specifically, some tools I'm using are:
-
Next.js: I decided on Next.js as my framework since it supports a variety of useful front-end tech natively (Typescript/TSX, Postcss, CSS Modules, Web Vitals, etc).
-
Style Dictionary: I'm using Style Dictionary to incorporate design tokens into my development flow; this helps keep design decisions consistent across all environments and reduces development time.
// style-dictionary.config.js (Style Dictionary config file)
module.exports = {
source: ['src/tokens/**/*.tokens.json'],
platforms: {
scss: {
transforms: ['attribute/cti', 'name/ti/camel'],
transformGroup: 'scss',
buildPath: 'src/styles/scss/',
files: tokenFileGenerator.baseTokens.map((tokenCategory) => ({
destination: `_${tokenCategory}.module.scss`,
format: 'scss/variables',
filter: tokenFilter(`${tokenCategory}`),
})),
},
},
};
// tokens/color/base.tokens.json (Color tokens)
{
"color": {
"blue-100": {
"value": "#99C7F2",
"category": "color"
// becomes $blue100 in _color.module.scss
},
"blue-200": {
"value": "#4D9EE9",
"category": "color"
// becomes $blue200 in _color.module.scss
},
"blue-300": {
"value": "#0074DF",
"category": "color"
// becomes $blue300 in _color.module.scss
},
"blue-400": {
"value": "#004686",
"category": "color"
},
"blue-500": {
"value": "#002343",
"category": "color"
}
}
-
CSS Modules: CSS Modules are helping me easily import CSS styling for multiple components and layouts without namespace collisions.
-
Class Variance Authority: CVA allows me to define variants of each component and generate class names based on which variant is invoked. Generated class names match those imported from the CSS module for any respective component.
-
Storybook: While static components are being maintained in Figma, I also build out each one and store them in Storybook. This helps make development much easier since I can modify components as needed and see how they interact with each other before publishing.
An example of my workflow using my Tilted Image Card component:
// TiltedImageCard.module.scss (Component stylesheet)
.TiltedImageCard {
@include main.flex-column;
justify-content: flex-end;
align-items: flex-start;
overflow: hidden;
box-shadow: 0px 0px 10px 0px rgba(184, 184, 184, 0.64);
border-radius: 12px;
}
.small {
width: 9.8125rem;
max-width: 9.8125rem;
height: 7.75rem;
}
.small > .cardTitle {
font-size: $fontSize16;
}
.large {
width: 11.775rem;
max-width: 11.775rem;
height: 9.3rem;
}
.large > .cardTitle {
font-size: typography.$fontSize18;
}
.cardTitle {
font-family: $fontFamilyMain;
font-weight: $fontWeightBold;
letter-spacing: $letterSpacingRegular;
color: $white300;
padding: $spacing8;
position: absolute;
line-height: normal;
align-self: stretch;
}
// TiltedImageCard.tsx (Component file)
import styles from './TiltedImageCard.module.scss';
// Using Class Variance Authority to declare
// component variants and their associated class names:
const tiltedImageClasses = cva(styles.TiltedImageCard, {
variants: {
size: {
// Class names for this variant will
// include ".TiltedImageCard .small"
small: styles.small,
large: styles.large,
},
tiltDirection: {
left: styles.leftTilted,
right: styles.rightTilted,
},
},
defaultVariants: {
size: 'small',
},
});
// Defining my variant categories as component
// props, along with the props 'title' and 'mediaSource':
interface TiltedImageCardProps extends VariantProps<typeof tiltedImageClasses> {
title?: String;
mediaSource: StaticImageData;
}
// The component with its props being
// passed in:
export default function TiltedImageCard({
size,
tiltDirection,
title,
mediaSource,
}: TiltedImageCardProps) {
return (
<article className={tiltedImageClasses({ tiltDirection, size })}>
<div className={styles.cardGradient}></div>
<img className={styles.cardMedia} src={mediaSource.src}></img>
<p className={styles.cardTitle}>{title}</p>
</article>
);
}
// TiltedImageCard.stories.tsx (Storybook file)
export const TiltedImages: Story = {
render: () => (
<TiltedImageCard
tiltDirection={'right'}
title={'New York City'}
mediaSource={photoNewYork}
></TiltedImageCard>
<TiltedImageCard
tiltDirection={'left'}
title={'Los Angeles'}
mediaSource={photoLosAngeles}
></TiltedImageCard>
<TiltedImageCard
size={'large'}
tiltDirection={'right'}
title={'London'}
mediaSource={photoLondon}
></TiltedImageCard>
<TiltedImageCard
size={'large'}
tiltDirection={'left'}
mediaSource={photoVesuvioBakery}
></TiltedImageCard>
),
args: {},
};
...and all of that eventually becomes 🧙🏾♂️ 🪄