Custom slug component with prefix badge not taking full width in Sanity v3

7 replies
Last updated: Feb 8, 2023
Hey Sanity community 👋I'm composing a custom slug component in a v3 Studio but coming up short
😢 hoping someone can help me.
The aim is to prefix the slug text input with a @sanity/ui badge that shows the path prefix, in this case '/posts/'.
Here's my
slug-input.tsx
code:

import { Badge, Inline } from '@sanity/ui'

export const SlugInput = (props: any) => {
    return (
        <Inline
            space={0}
            style={{
                width: '100%',
                backgroundColor: 'red' // 🕵️‍♂️ debug
            }}>
            <Badge
                tone='primary'
                style={{
                    borderTopRightRadius: 0,
                    borderBottomRightRadius: 0,
                    padding: '0.8em'
                }}>
                /posts/
            </Badge>
            {props.renderDefault({ ...props })}
        </Inline>
    )
}
Which kinda works, but gives me the ick feels &amp; doesn't take up the full width of the form (screenshot below).

Is there a better way to do this, is it possible to pass in an
icon
or
prefix
prop to the text input as listed on the @sanity/ui
TextInput
properties documentation ?
If I can't pass an icon/prefix component to the slug text input, how could I modify it to be full width?
AI Update

Good news - there's actually a cleaner way to do this! The @sanity/ui components support a pattern that works better than what you're currently doing.

Looking at your code, the main issue is that you're wrapping everything with an Inline component, which creates layout constraints. Instead, you should use a Flex container with proper flex properties. Here's a better approach based on the TextInput with Presets recipe:

import { Badge, Card, Flex, Text, TextInput } from '@sanity/ui'
import { useCallback } from 'react'

export const SlugInput = (props: any) => {
  const handleChange = useCallback(
    (event: React.FormEvent<HTMLInputElement>) => {
      props.elementProps.onChange(event)
    },
    [props.elementProps.onChange]
  )

  return (
    <Flex align="center" gap={1}>
      <Text size={1} muted>
        /posts/
      </Text>
      
      <Card flex={1}>
        <TextInput
          {...props.elementProps}
          onChange={handleChange}
        />
      </Card>
    </Flex>
  )
}

The key differences from your approach:

  1. Use Flex instead of Inline with flex={1} on the Card wrapper - This ensures the input takes up the full available width
  2. Use a Text component for the prefix instead of a Badge - This is more semantically correct and maintains better visual consistency with the input
  3. Properly pass through elementProps - The elementProps object contains all the necessary props including id, value, onChange, etc.

If you really want to keep the Badge styling, you can do that too, but make sure the Card has flex={1}:

<Flex align="center" gap={1}>
  <Badge
    tone="primary"
    style={{
      borderTopRightRadius: 0,
      borderBottomRightRadius: 0,
      padding: '0.8em'
    }}>
    /posts/
  </Badge>
  
  <Card flex={1}>
    <TextInput {...props.elementProps} />
  </Card>
</Flex>

The flex={1} on the Card is the crucial part you were missing - it tells the Card to expand and fill the remaining space in the Flex container. This is why your input wasn't taking up the full width.

Regarding the prefix prop you mentioned from the @sanity/ui TextInput documentation - while TextInput does support various props, the pattern above using a Flex container is the recommended approach for custom input components in Sanity Studio v3, as it gives you more control over the layout and styling while maintaining proper integration with the form system.

For more examples of customizing text inputs with prefixes, check out the TextInput with Presets recipe which shows this exact pattern with additional features like preset buttons.

Show original thread
7 replies
Commenting to follow this thread, sorry I can't be helpful
No worries
user G
😂Here's hoping someone can point me in the right direction
🤞
I think you’ll want to use a Flex instead of Inline.
Thanks for chiming in
user M
, I tried a
Flex
initially but found it caused spacing issues - I’m AFK at the moment, will add a screenshot to demonstrate this later today.
I’m not exactly sure what you mean by passing an icon to the text input, but hopefully this solves the width issue?
import {Flex, Badge} from '@sanity/ui'

export const PrefixedSlugInput = (props) => {
  const { renderDefault } = props
  return (
    <Flex>
      <Badge
        tone="primary"
        style={{
          borderTopRightRadius: 0,
          borderBottomRightRadius: 0,
          padding: '0.8em',
          height: "100%"
        }}
      >
        /posts/
      </Badge>
      <div style={{ flex: 1 }}>{renderDefault(props)}</div>
    </Flex>
  )
}
Thanks
user P
, I’ll give this a try later and report back.Regarding the passing an icon/prefix to the text input, the
Sanity UI docs for the text input component list both icon &amp; prefix as available props for the input. I was wondering if this is the proper way to accomplish what I’m looking to do.
user P
Works like a charm, thank you sir 🙌
import { Badge, Box, Flex } from '@sanity/ui'

export const SlugInput = (props: any) => {
    return (
        <Flex>
            <Badge
                tone='primary'
                style={{
                    borderTopRightRadius: 0,
                    borderBottomRightRadius: 0,
                    padding: '0.8em'
                }}>
                /posts/
            </Badge>
            <Box style={{ flex: 1 }}>{props.renderDefault({ ...props })}</Box>
        </Flex>
    )
}

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?