Skip to content

Instantly share code, notes, and snippets.

@karol-majewski
Created November 25, 2025 00:55
Show Gist options
  • Select an option

  • Save karol-majewski/7bbecda290b488a9d2525597b10a8d29 to your computer and use it in GitHub Desktop.

Select an option

Save karol-majewski/7bbecda290b488a9d2525597b10a8d29 to your computer and use it in GitHub Desktop.

Recreate trim functionality without Radix themes

Exported on 11/25/2025 at 01:52:39 GMT+1 from Cursor (2.1.25)


User

Radix UI supports a functionality called trim. Here's a link describing it. How can I recreate exactly the same behavior without using Radix themes?

https://www.radix-ui.com/themes/docs/components/text


Cursor

Reviewing the current typography styles to understand the setup.

Recreating Radix UI's trim using negative margins to trim leading space. Here's a CSS implementation you can add to your typography styles:

/* Leading trim utilities - Recreates Radix UI trim functionality */
/* Uses negative margins to trim leading space, similar to half-leading control */

:root {
  /* Default trim values - adjust these based on your font stack */
  --leading-trim-start: 0.42em;
  --leading-trim-end: 0.36em;
  
  /* Heading-specific trim values */
  --heading-leading-trim-start: 0.42em;
  --heading-leading-trim-end: 0.36em;
}

/* Trim start - removes leading space at the top */
@utility trim-start {
  margin-top: calc(-1 * var(--leading-trim-start));
}

/* Trim end - removes leading space at the bottom */
@utility trim-end {
  margin-bottom: calc(-1 * var(--leading-trim-end));
}

/* Trim both - removes leading space at top and bottom */
@utility trim-both {
  margin-top: calc(-1 * var(--leading-trim-start));
  margin-bottom: calc(-1 * var(--leading-trim-end));
}

/* Heading-specific trim utilities */
@utility trim-heading-start {
  margin-top: calc(-1 * var(--heading-leading-trim-start));
}

@utility trim-heading-end {
  margin-bottom: calc(-1 * var(--heading-leading-trim-end));
}

@utility trim-heading-both {
  margin-top: calc(-1 * var(--heading-leading-trim-start));
  margin-bottom: calc(-1 * var(--heading-leading-trim-end));
}

Usage examples:

// Trim start only
<p className="trim-start">Text with trimmed top leading</p>

// Trim end only  
<p className="trim-end">Text with trimmed bottom leading</p>

// Trim both
<p className="trim-both">Text with trimmed top and bottom leading</p>

// For headings
<h1 className="trim-heading-both">Heading with trimmed leading</h1>

How it works:

  • Uses negative margins to offset the leading space
  • trim-start removes top leading
  • trim-end removes bottom leading
  • trim-both removes both
  • CSS variables let you adjust values per font

Adjusting for your fonts: If you use custom fonts (e.g., Freight Neo), you may need to tune the CSS variables. Test visually and adjust --leading-trim-start and --leading-trim-end until spacing matches your design.

Should I add this to your typography.css file, or do you want to adjust the values first?

/* Leading trim utilities - Recreates Radix UI trim functionality */
/* Uses negative margins to trim leading space, similar to half-leading control */

:root {
  /* Default trim values - adjust these based on your font stack */
  --leading-trim-start: 0.42em;
  --leading-trim-end: 0.36em;
  
  /* Heading-specific trim values */
  --heading-leading-trim-start: 0.42em;
  --heading-leading-trim-end: 0.36em;
}

/* Trim start - removes leading space at the top */
@utility trim-start {
  margin-top: calc(-1 * var(--leading-trim-start));
}

/* Trim end - removes leading space at the bottom */
@utility trim-end {
  margin-bottom: calc(-1 * var(--leading-trim-end));
}

/* Trim both - removes leading space at top and bottom */
@utility trim-both {
  margin-top: calc(-1 * var(--leading-trim-start));
  margin-bottom: calc(-1 * var(--leading-trim-end));
}

/* Heading-specific trim utilities */
@utility trim-heading-start {
  margin-top: calc(-1 * var(--heading-leading-trim-start));
}

@utility trim-heading-end {
  margin-bottom: calc(-1 * var(--heading-leading-trim-end));
}

@utility trim-heading-both {
  margin-top: calc(-1 * var(--heading-leading-trim-start));
  margin-bottom: calc(-1 * var(--heading-leading-trim-end));
}
// Trim start only
<p className="trim-start">Text with trimmed top leading</p>

// Trim end only  
<p className="trim-end">Text with trimmed bottom leading</p>

// Trim both
<p className="trim-both">Text with trimmed top and bottom leading</p>

// For headings
<h1 className="trim-heading-both">Heading with trimmed leading</h1>

User

How should I go about adjusting custom values if I'm using multiple fonts and multiple weights?


Cursor

Checking which fonts and weights are used in the project to tailor the trim values.

Here’s a practical approach for handling trim values across multiple fonts and weights:

Strategy: Scoped CSS Custom Properties

Use CSS custom properties scoped by font family and optionally by weight. This keeps values organized and easy to adjust.

/* Leading trim utilities - Recreates Radix UI trim functionality */

:root {
  /* Default trim values - fallback for system fonts */
  --leading-trim-start: 0.42em;
  --leading-trim-end: 0.36em;
  
  /* Museum font (your local font) */
  --leading-trim-start-museum: 0.42em;
  --leading-trim-end-museum: 0.36em;
  
  /* Museum font by weight */
  --leading-trim-start-museum-400: 0.42em;
  --leading-trim-end-museum-400: 0.36em;
  --leading-trim-start-museum-500: 0.42em;
  --leading-trim-end-museum-500: 0.36em;
  --leading-trim-start-museum-700: 0.45em; /* Bold often needs more trim */
  --leading-trim-end-museum-700: 0.38em;
  
  /* Freight Neo (Adobe Fonts) */
  --leading-trim-start-freight-neo: 0.40em;
  --leading-trim-end-freight-neo: 0.35em;
  
  /* Freight Text Pro */
  --leading-trim-start-freight-text: 0.43em;
  --leading-trim-end-freight-text: 0.37em;
  
  /* Freight Sans Pro */
  --leading-trim-start-freight-sans: 0.41em;
  --leading-trim-end-freight-sans: 0.36em;
  
  /* Monospace - usually needs less trim */
  --leading-trim-start-monospace: 0.35em;
  --leading-trim-end-monospace: 0.30em;
}

/* Scoped by font-family using CSS cascade */
.font-museum {
  --leading-trim-start: var(--leading-trim-start-museum);
  --leading-trim-end: var(--leading-trim-end-museum);
}

.font-freight-neo {
  --leading-trim-start: var(--leading-trim-start-freight-neo);
  --leading-trim-end: var(--leading-trim-end-freight-neo);
}

.font-freight-text {
  --leading-trim-start: var(--leading-trim-start-freight-text);
  --leading-trim-end: var(--leading-trim-end-freight-text);
}

.font-freight-sans {
  --leading-trim-start: var(--leading-trim-start-freight-sans);
  --leading-trim-end: var(--leading-trim-end-freight-sans);
}

.font-monospace {
  --leading-trim-start: var(--leading-trim-start-monospace);
  --leading-trim-end: var(--leading-trim-end-monospace);
}

/* Weight-specific overrides (more specific = higher priority) */
.font-museum.font-weight-400 {
  --leading-trim-start: var(--leading-trim-start-museum-400);
  --leading-trim-end: var(--leading-trim-end-museum-400);
}

.font-museum.font-weight-500 {
  --leading-trim-start: var(--leading-trim-start-museum-500);
  --leading-trim-end: var(--leading-trim-end-museum-500);
}

.font-museum.font-weight-700 {
  --leading-trim-start: var(--leading-trim-start-museum-700);
  --leading-trim-end: var(--leading-trim-end-museum-700);
}

/* Base trim utilities - use CSS variables that cascade */
@utility trim-start {
  margin-top: calc(-1 * var(--leading-trim-start));
}

@utility trim-end {
  margin-bottom: calc(-1 * var(--leading-trim-end));
}

@utility trim-both {
  margin-top: calc(-1 * var(--leading-trim-start));
  margin-bottom: calc(-1 * var(--leading-trim-end));
}

How to Measure and Adjust Values

Step 1: Visual inspection method

  1. Create a test page with bordered containers:
<div className="border-2 border-dashed p-4">
  <p className="font-museum font-normal trim-both">
    Test text with trim
  </p>
</div>
  1. Compare with and without trim — the padding should look equal on all sides when trimmed.

Step 2: Browser DevTools method

  1. Inspect the text element and check Computed → line-height.
  2. Measure the visual gap above/below the text.
  3. Calculate: trim-value = (line-height - font-size) / 2 (rough starting point).
  4. Adjust in 0.01em increments until spacing looks balanced.

Step 3: Systematic testing

Create a test component to compare all combinations:

// Test component (temporary, for measurement)
const fonts = ['museum', 'freight-neo', 'freight-text', 'freight-sans'];
const weights = [400, 500, 700];

fonts.map(font => 
  weights.map(weight => (
    <div key={`${font}-${weight}`} className="border p-4">
      <p className={`font-${font} font-weight-${weight} trim-both`}>
        {font} {weight}
      </p>
    </div>
  ))
)

Alternative: Data Attribute Approach

If you prefer explicit control per element:

/* Data attribute scoping */
[data-font="museum"][data-weight="400"] {
  --leading-trim-start: 0.42em;
  --leading-trim-end: 0.36em;
}

[data-font="museum"][data-weight="700"] {
  --leading-trim-start: 0.45em;
  --leading-trim-end: 0.38em;
}

Usage:

<p data-font="museum" data-weight="700" className="trim-both">
  Bold Museum text
</p>

Recommended Workflow

  1. Start with Radix defaults (0.42em/0.36em) for all fonts.
  2. Test visually with bordered containers.
  3. Adjust per font family first (largest differences).
  4. Then adjust per weight if needed (bold often needs more).
  5. Document your final values in comments.

Pro Tips

  • Bold weights often need 0.02–0.05em more trim.
  • Serif fonts may differ from sans-serif.
  • Italic variants may need slight adjustments.
  • Test at different font sizes — trim values scale with em.
  • Use browser DevTools to toggle trim classes and compare.

Should I create a test page component to help you measure these values, or do you want to start with the CSS structure above and adjust as needed?

/* Leading trim utilities - Recreates Radix UI trim functionality */

:root {
  /* Default trim values - fallback for system fonts */
  --leading-trim-start: 0.42em;
  --leading-trim-end: 0.36em;
  
  /* Museum font (your local font) */
  --leading-trim-start-museum: 0.42em;
  --leading-trim-end-museum: 0.36em;
  
  /* Museum font by weight */
  --leading-trim-start-museum-400: 0.42em;
  --leading-trim-end-museum-400: 0.36em;
  --leading-trim-start-museum-500: 0.42em;
  --leading-trim-end-museum-500: 0.36em;
  --leading-trim-start-museum-700: 0.45em; /* Bold often needs more trim */
  --leading-trim-end-museum-700: 0.38em;
  
  /* Freight Neo (Adobe Fonts) */
  --leading-trim-start-freight-neo: 0.40em;
  --leading-trim-end-freight-neo: 0.35em;
  
  /* Freight Text Pro */
  --leading-trim-start-freight-text: 0.43em;
  --leading-trim-end-freight-text: 0.37em;
  
  /* Freight Sans Pro */
  --leading-trim-start-freight-sans: 0.41em;
  --leading-trim-end-freight-sans: 0.36em;
  
  /* Monospace - usually needs less trim */
  --leading-trim-start-monospace: 0.35em;
  --leading-trim-end-monospace: 0.30em;
}

/* Scoped by font-family using CSS cascade */
.font-museum {
  --leading-trim-start: var(--leading-trim-start-museum);
  --leading-trim-end: var(--leading-trim-end-museum);
}

.font-freight-neo {
  --leading-trim-start: var(--leading-trim-start-freight-neo);
  --leading-trim-end: var(--leading-trim-end-freight-neo);
}

.font-freight-text {
  --leading-trim-start: var(--leading-trim-start-freight-text);
  --leading-trim-end: var(--leading-trim-end-freight-text);
}

.font-freight-sans {
  --leading-trim-start: var(--leading-trim-start-freight-sans);
  --leading-trim-end: var(--leading-trim-end-freight-sans);
}

.font-monospace {
  --leading-trim-start: var(--leading-trim-start-monospace);
  --leading-trim-end: var(--leading-trim-end-monospace);
}

/* Weight-specific overrides (more specific = higher priority) */
.font-museum.font-weight-400 {
  --leading-trim-start: var(--leading-trim-start-museum-400);
  --leading-trim-end: var(--leading-trim-end-museum-400);
}

.font-museum.font-weight-500 {
  --leading-trim-start: var(--leading-trim-start-museum-500);
  --leading-trim-end: var(--leading-trim-end-museum-500);
}

.font-museum.font-weight-700 {
  --leading-trim-start: var(--leading-trim-start-museum-700);
  --leading-trim-end: var(--leading-trim-end-museum-700);
}

/* Base trim utilities - use CSS variables that cascade */
@utility trim-start {
  margin-top: calc(-1 * var(--leading-trim-start));
}

@utility trim-end {
  margin-bottom: calc(-1 * var(--leading-trim-end));
}

@utility trim-both {
  margin-top: calc(-1 * var(--leading-trim-start));
  margin-bottom: calc(-1 * var(--leading-trim-end));
}
<div className="border-2 border-dashed p-4">
  <p className="font-museum font-normal trim-both">
    Test text with trim
  </p>
</div>
// Test component (temporary, for measurement)
const fonts = ['museum', 'freight-neo', 'freight-text', 'freight-sans'];
const weights = [400, 500, 700];

fonts.map(font => 
  weights.map(weight => (
    <div key={`${font}-${weight}`} className="border p-4">
      <p className={`font-${font} font-weight-${weight} trim-both`}>
        {font} {weight}
      </p>
    </div>
  ))
)
/* Data attribute scoping */
[data-font="museum"][data-weight="400"] {
  --leading-trim-start: 0.42em;
  --leading-trim-end: 0.36em;
}

[data-font="museum"][data-weight="700"] {
  --leading-trim-start: 0.45em;
  --leading-trim-end: 0.38em;
}
<p data-font="museum" data-weight="700" className="trim-both">
  Bold Museum text
</p>

User

Is there some canonical CSS property that does exactly the same thing without me having to use any hacks?


Cursor

Searching for native CSS properties that handle leading trim without hacks.

There are two CSS properties that handle this natively:

1. text-box-trim (most direct equivalent)

Designed to trim leading space in text boxes, matching Radix UI's trim behavior:

.text-element {
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;
}

Values:

  • text-box-trim: trim-start | trim-end | trim-both | none
  • text-box-edge: Defines the reference edges (e.g., cap alphabetic for cap height top, alphabetic baseline bottom)

Example:

@utility trim-start {
  text-box-trim: trim-start;
  text-box-edge: cap alphabetic;
}

@utility trim-end {
  text-box-trim: trim-end;
  text-box-edge: cap alphabetic;
}

@utility trim-both {
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;
}

2. margin-trim (container-level)

Trims child margins at container edges:

.container {
  margin-trim: block; /* trims top/bottom margins */
}

Browser Support & Fallback Strategy

Both are experimental. Use progressive enhancement with a fallback:

/* Leading trim utilities with progressive enhancement */

/* Modern browsers: use native text-box-trim */
@utility trim-start {
  text-box-trim: trim-start;
  text-box-edge: cap alphabetic;
  
  /* Fallback for browsers without support */
  @supports not (text-box-trim: trim-start) {
    margin-top: calc(-1 * var(--leading-trim-start, 0.42em));
  }
}

@utility trim-end {
  text-box-trim: trim-end;
  text-box-edge: cap alphabetic;
  
  @supports not (text-box-trim: trim-end) {
    margin-bottom: calc(-1 * var(--leading-trim-end, 0.36em));
  }
}

@utility trim-both {
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;
  
  @supports not (text-box-trim: trim-both) {
    margin-top: calc(-1 * var(--leading-trim-start, 0.42em));
    margin-bottom: calc(-1 * var(--leading-trim-end, 0.36em));
  }
}

Current Browser Support Status

  • text-box-trim: Limited support (Safari 17.4+, Chrome/Edge behind flags)
  • margin-trim: Limited support (Safari 16.4+)

Recommendation

Use text-box-trim with the negative margin fallback. This gives you:

  1. Native behavior where supported
  2. Fallback for older browsers
  3. Future-proof code

The @supports query ensures the fallback only applies when needed, so modern browsers use the native property.

Should I update your typography.css with this progressive enhancement approach?

.text-element {
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;
}
@utility trim-start {
  text-box-trim: trim-start;
  text-box-edge: cap alphabetic;
}

@utility trim-end {
  text-box-trim: trim-end;
  text-box-edge: cap alphabetic;
}

@utility trim-both {
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;
}
.container {
  margin-trim: block; /* trims top/bottom margins */
}
/* Leading trim utilities with progressive enhancement */

/* Modern browsers: use native text-box-trim */
@utility trim-start {
  text-box-trim: trim-start;
  text-box-edge: cap alphabetic;
  
  /* Fallback for browsers without support */
  @supports not (text-box-trim: trim-start) {
    margin-top: calc(-1 * var(--leading-trim-start, 0.42em));
  }
}

@utility trim-end {
  text-box-trim: trim-end;
  text-box-edge: cap alphabetic;
  
  @supports not (text-box-trim: trim-end) {
    margin-bottom: calc(-1 * var(--leading-trim-end, 0.36em));
  }
}

@utility trim-both {
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;
  
  @supports not (text-box-trim: trim-both) {
    margin-top: calc(-1 * var(--leading-trim-start, 0.42em));
    margin-bottom: calc(-1 * var(--leading-trim-end, 0.36em));
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment