Dev Tips
Read the latest tips and tricks for building your SaaS. Get fresh developer tips every week.
Filter tips:
Use input type for better mobile keyboards
#html #frontend #a11y #mobile #ux
On mobile devices, the type
attribute of an <input>
not only validates data but also brings up context-aware keyboards.
This makes forms easier and faster to use.
<!-- Brings up numeric keypad -->
<input type="number" placeholder="Age" />
<!-- Brings up phone dialer keypad -->
<input type="tel" placeholder="Phone number" />
<!-- Brings up email keyboard with @ and .com -->
<input type="email" placeholder="Email" />
<!-- Brings up URL keyboard with / and . keys -->
<input type="url" placeholder="Website" />
<!-- Brings up date picker -->
<input type="date" placeholder="Birthday" />
<!-- Brings up time picker -->
<input type="time" placeholder="Time" />
<!-- Brings up search keyboard -->
<input type="search" placeholder="Search" />
<!-- ... and more! -->
Using the right input type improves UX, reduces input errors, and makes your app feel more polished on mobile devices.
Swap Lodash for es-toolkit for leaner, faster utils
#typescript #performance

Looking to slim down your bundle and speed up utility functions?
Meet es-toolkit — a modern, lightweight replacement for Lodash.
Simply swap imports like this:
// old Lodash import
import { pick } from 'lodash-es';
// new es-toolkit import
import { pick } from 'es-toolkit';
Why you'll love es-toolkit:
- Tiny bundle size — up to 97% smaller than Lodash equivalents.
- Blazing fast performance — often 2–3× faster, with some functions up to 11× faster.
- Complete Lodash compatibility — via the es-toolkit/compat layer — seamless migration.
✨ Bonus: Utilities Lodash doesn’t have
es-toolkit also ships with handy functions that Lodash never included:
import { clamp, toggle, partitionObject } from 'es-toolkit';
// Clamp numbers between a min and max
clamp(150, 0, 100);
// → 100
// Toggle a boolean value
toggle(true);
// → false
// Partition an object into [matching, rest]
partitionObject({ a: 1, b: 2, c: 3 }, (val) => val > 1);
// → [{ b: 2, c: 3 }, { a: 1 }]
// Attempt a promise and return data or error without throwing
const [error, data] = await attemptAsync(async () => {
const response = await fetch('https://api.example.com/data');
return response.json();
});
// → { data: { ... } } or { error: 'Error message' }
// ... and more!
Truncate text with Tailwind's line-clamp utility
#tailwind #css #styling #design

Long text can easily break your layout or push important content out of view.
Instead of writing custom CSS for truncation, Tailwind gives you the line-clamp
utility out of the box,
which will truncate the text to a given number of lines and replace the rest with an ellipsis.
<p className="line-clamp-2">
This is a very long paragraph that will be truncated
after two lines. The rest of the content will not be
displayed but replaced with an ellipsis.
</p>
line-clamp-1
→ truncates after one lineline-clamp-2
→ truncates after two linesline-clamp-3
→ truncates after three lines, and so on
This is perfect for previews (like blog post cards or product descriptions) where you only want to show a snippet.
Use skipToken for type-safe query disabling in TanStack Query
#tanstack #react #typescript
When using useQuery
, you may want to disable fetching until a condition is met. While enabled: false
works, it doesn't narrow types inside your queryFn
—TypeScript still sees the parameter as possibly undefined.
For a type-safe alternative, use skipToken
:
import { useQuery, skipToken } from '@tanstack/react-query';
function Todos() {
const [filter, setFilter] = React.useState<string | undefined>();
const { data } = useQuery({
queryKey: ['todos', filter],
// Type-safe: skip query when `filter` is undefined
queryFn: filter ? () => fetchTodos(filter) : skipToken,
});
return (
<div>
<FiltersForm onApply={setFilter} />
{data && <TodosTable data={data} />}
</div>
);
}
Use .nullish() instead of .optional().nullable() in zod
#zod #typescript
When working with zod, you might have found yourself using .optional().nullable()
to make a field optional and nullable.
Especially when you are working with tRPC or similar API libraries, this is a common case for API input validation.
But there is a better way to do this.
Instead of using .optional().nullable()
, you can use .nullish()
.
const schema = z.object({
name: z.string().optional().nullable(), // ❌ don't do this
name: z.string().nullish(), // ✅ do this instead
});
Create Tailwind CSS shades from your brand colors
#tailwind #css #styling #design

When using Tailwind CSS to style your UI, you might have noticed that they use a shade system for colors. Usually when you pick brand colors though, you don't necessarily pick all shades for them but only a few specific colors.
To create shades that you can easily use in your Tailwind CSS classes and therefore in your UI, you can use the uicolors.app tool.
It allows you to enter the hex code of your brand color and will generate all the shades for you, as well as give you recommendations on what shades to use where in your UI including contrast ratios.
Start your scalable and production-ready SaaS today
Save endless hours of development time and focus on what's important for your customers with our SaaS starter kits for Next.js, Nuxt 3 and SvelteKit
Get startedStay up to date
Sign up for our newsletter and we will keep you updated on everything going on with supastarter.